From 7167ce41b61d2ba2cdb526777a4233eb84a3b66a Mon Sep 17 00:00:00 2001 From: Unit 193 Date: Sat, 6 Dec 2014 17:33:25 -0500 Subject: Imported Upstream version 2.99.6 --- Plugins/ConfigMigration/ConfigMigration.pro | 31 + Plugins/ConfigMigration/config_migration.png | Bin 0 -> 24844 bytes Plugins/ConfigMigration/configmigartion.json | 0 Plugins/ConfigMigration/configmigration.cpp | 188 + Plugins/ConfigMigration/configmigration.h | 49 + Plugins/ConfigMigration/configmigration.json | 8 + Plugins/ConfigMigration/configmigration.qrc | 5 + Plugins/ConfigMigration/configmigration_global.h | 12 + Plugins/ConfigMigration/configmigrationitem.h | 20 + Plugins/ConfigMigration/configmigrationwizard.cpp | 407 ++ Plugins/ConfigMigration/configmigrationwizard.h | 52 + Plugins/ConfigMigration/configmigrationwizard.ui | 109 + Plugins/CsvExport/CsvExport.pro | 29 + Plugins/CsvExport/CsvExport.ui | 100 + Plugins/CsvExport/csvexport.cpp | 191 + Plugins/CsvExport/csvexport.h | 56 + Plugins/CsvExport/csvexport.json | 7 + Plugins/CsvExport/csvexport.qrc | 5 + Plugins/CsvExport/csvexport_global.h | 12 + Plugins/CsvImport/CsvImport.pro | 28 + Plugins/CsvImport/CsvImportOptions.ui | 103 + Plugins/CsvImport/csvimport.cpp | 206 + Plugins/CsvImport/csvimport.h | 55 + Plugins/CsvImport/csvimport.json | 7 + Plugins/CsvImport/csvimport.qrc | 5 + Plugins/CsvImport/csvimport_global.h | 12 + Plugins/DbSqlite2/DbSqlite2.pro | 26 + Plugins/DbSqlite2/dbsqlite2.cpp | 51 + Plugins/DbSqlite2/dbsqlite2.h | 23 + Plugins/DbSqlite2/dbsqlite2.json | 7 + Plugins/DbSqlite2/dbsqlite2_global.h | 12 + Plugins/DbSqlite2/dbsqlite2instance.cpp | 7 + Plugins/DbSqlite2/dbsqlite2instance.h | 27 + Plugins/HtmlExport/HtmlExport.pro | 29 + Plugins/HtmlExport/htmlexport.cpp | 615 +++ Plugins/HtmlExport/htmlexport.css | 59 + Plugins/HtmlExport/htmlexport.h | 74 + Plugins/HtmlExport/htmlexport.json | 7 + Plugins/HtmlExport/htmlexport.qrc | 8 + Plugins/HtmlExport/htmlexport.ui | 129 + Plugins/HtmlExport/htmlexport_global.h | 12 + Plugins/JsonExport/JsonExport.pro | 28 + Plugins/JsonExport/jsonexport.cpp | 453 ++ Plugins/JsonExport/jsonexport.h | 78 + Plugins/JsonExport/jsonexport.json | 7 + Plugins/JsonExport/jsonexport.qrc | 5 + Plugins/JsonExport/jsonexport.ui | 66 + Plugins/JsonExport/jsonexport_global.h | 12 + Plugins/PdfExport/PdfExport.pro | 26 + Plugins/PdfExport/pdfexport.cpp | 1420 ++++++ Plugins/PdfExport/pdfexport.h | 225 + Plugins/PdfExport/pdfexport.json | 8 + Plugins/PdfExport/pdfexport.qrc | 5 + Plugins/PdfExport/pdfexport.ui | 303 ++ Plugins/PdfExport/pdfexport_global.h | 12 + Plugins/Plugins.pro | 19 + Plugins/Printing/Printing.pro | 35 + Plugins/Printing/printer.png | Bin 0 -> 715 bytes Plugins/Printing/printing.cpp | 111 + Plugins/Printing/printing.h | 36 + Plugins/Printing/printing.json | 9 + Plugins/Printing/printing.qrc | 5 + Plugins/Printing/printing_global.h | 12 + Plugins/Printing/printingexport.cpp | 46 + Plugins/Printing/printingexport.h | 28 + Plugins/RegExpImport/RegExpImport.pro | 28 + Plugins/RegExpImport/regexpimport.cpp | 209 + Plugins/RegExpImport/regexpimport.h | 52 + Plugins/RegExpImport/regexpimport.json | 7 + Plugins/RegExpImport/regexpimport.qrc | 5 + Plugins/RegExpImport/regexpimport.ui | 99 + Plugins/RegExpImport/regexpimport_global.h | 12 + Plugins/ScriptingTcl/ScriptingTcl.pro | 127 + Plugins/ScriptingTcl/scriptingtcl.cpp | 656 +++ Plugins/ScriptingTcl/scriptingtcl.h | 111 + Plugins/ScriptingTcl/scriptingtcl.json | 7 + Plugins/ScriptingTcl/scriptingtcl.png | Bin 0 -> 1104 bytes Plugins/ScriptingTcl/scriptingtcl.qrc | 5 + Plugins/ScriptingTcl/scriptingtcl_global.h | 12 + .../SqlEnterpriseFormatter.pro | 100 + .../SqlEnterpriseFormatter/formataltertable.cpp | 31 + Plugins/SqlEnterpriseFormatter/formataltertable.h | 20 + Plugins/SqlEnterpriseFormatter/formatanalyze.cpp | 18 + Plugins/SqlEnterpriseFormatter/formatanalyze.h | 20 + Plugins/SqlEnterpriseFormatter/formatattach.cpp | 22 + Plugins/SqlEnterpriseFormatter/formatattach.h | 20 + .../SqlEnterpriseFormatter/formatbegintrans.cpp | 24 + Plugins/SqlEnterpriseFormatter/formatbegintrans.h | 20 + .../SqlEnterpriseFormatter/formatcolumntype.cpp | 43 + Plugins/SqlEnterpriseFormatter/formatcolumntype.h | 20 + .../SqlEnterpriseFormatter/formatcommittrans.cpp | 24 + Plugins/SqlEnterpriseFormatter/formatcommittrans.h | 20 + Plugins/SqlEnterpriseFormatter/formatcopy.cpp | 24 + Plugins/SqlEnterpriseFormatter/formatcopy.h | 20 + .../SqlEnterpriseFormatter/formatcreateindex.cpp | 42 + Plugins/SqlEnterpriseFormatter/formatcreateindex.h | 20 + .../SqlEnterpriseFormatter/formatcreatetable.cpp | 224 + Plugins/SqlEnterpriseFormatter/formatcreatetable.h | 63 + .../SqlEnterpriseFormatter/formatcreatetrigger.cpp | 90 + .../SqlEnterpriseFormatter/formatcreatetrigger.h | 31 + .../SqlEnterpriseFormatter/formatcreateview.cpp | 26 + Plugins/SqlEnterpriseFormatter/formatcreateview.h | 20 + .../formatcreatevirtualtable.cpp | 106 + .../formatcreatevirtualtable.h | 23 + Plugins/SqlEnterpriseFormatter/formatdelete.cpp | 33 + Plugins/SqlEnterpriseFormatter/formatdelete.h | 20 + Plugins/SqlEnterpriseFormatter/formatdetach.cpp | 18 + Plugins/SqlEnterpriseFormatter/formatdetach.h | 20 + Plugins/SqlEnterpriseFormatter/formatdropindex.cpp | 20 + Plugins/SqlEnterpriseFormatter/formatdropindex.h | 20 + Plugins/SqlEnterpriseFormatter/formatdroptable.cpp | 21 + Plugins/SqlEnterpriseFormatter/formatdroptable.h | 20 + .../SqlEnterpriseFormatter/formatdroptrigger.cpp | 21 + Plugins/SqlEnterpriseFormatter/formatdroptrigger.h | 20 + Plugins/SqlEnterpriseFormatter/formatdropview.cpp | 20 + Plugins/SqlEnterpriseFormatter/formatdropview.h | 20 + Plugins/SqlEnterpriseFormatter/formatempty.cpp | 11 + Plugins/SqlEnterpriseFormatter/formatempty.h | 20 + Plugins/SqlEnterpriseFormatter/formatexpr.cpp | 201 + Plugins/SqlEnterpriseFormatter/formatexpr.h | 20 + .../SqlEnterpriseFormatter/formatforeignkey.cpp | 78 + Plugins/SqlEnterpriseFormatter/formatforeignkey.h | 33 + .../SqlEnterpriseFormatter/formatindexedcolumn.cpp | 17 + .../SqlEnterpriseFormatter/formatindexedcolumn.h | 20 + Plugins/SqlEnterpriseFormatter/formatinsert.cpp | 54 + Plugins/SqlEnterpriseFormatter/formatinsert.h | 20 + Plugins/SqlEnterpriseFormatter/formatlimit.cpp | 24 + Plugins/SqlEnterpriseFormatter/formatlimit.h | 19 + Plugins/SqlEnterpriseFormatter/formatorderby.cpp | 15 + Plugins/SqlEnterpriseFormatter/formatorderby.h | 19 + Plugins/SqlEnterpriseFormatter/formatpragma.cpp | 24 + Plugins/SqlEnterpriseFormatter/formatpragma.h | 20 + Plugins/SqlEnterpriseFormatter/formatraise.cpp | 16 + Plugins/SqlEnterpriseFormatter/formatraise.h | 20 + Plugins/SqlEnterpriseFormatter/formatreindex.cpp | 16 + Plugins/SqlEnterpriseFormatter/formatreindex.h | 20 + Plugins/SqlEnterpriseFormatter/formatrelease.cpp | 16 + Plugins/SqlEnterpriseFormatter/formatrelease.h | 20 + Plugins/SqlEnterpriseFormatter/formatrollback.cpp | 24 + Plugins/SqlEnterpriseFormatter/formatrollback.h | 20 + Plugins/SqlEnterpriseFormatter/formatsavepoint.cpp | 12 + Plugins/SqlEnterpriseFormatter/formatsavepoint.h | 20 + Plugins/SqlEnterpriseFormatter/formatselect.cpp | 261 + Plugins/SqlEnterpriseFormatter/formatselect.h | 103 + Plugins/SqlEnterpriseFormatter/formatstatement.cpp | 1064 ++++ Plugins/SqlEnterpriseFormatter/formatstatement.h | 198 + Plugins/SqlEnterpriseFormatter/formatupdate.cpp | 47 + Plugins/SqlEnterpriseFormatter/formatupdate.h | 20 + Plugins/SqlEnterpriseFormatter/formatvacuum.cpp | 11 + Plugins/SqlEnterpriseFormatter/formatvacuum.h | 20 + Plugins/SqlEnterpriseFormatter/formatwith.cpp | 38 + Plugins/SqlEnterpriseFormatter/formatwith.h | 31 + .../sqlenterpriseformatter.cpp | 112 + .../sqlenterpriseformatter.h | 84 + .../sqlenterpriseformatter.json | 7 + .../sqlenterpriseformatter.qrc | 5 + .../sqlenterpriseformatter.ui | 671 +++ .../sqlenterpriseformatter_global.h | 12 + Plugins/SqlExport/SqlExport.pro | 29 + Plugins/SqlExport/SqlExportCommon.ui | 51 + Plugins/SqlExport/SqlExportQuery.ui | 85 + Plugins/SqlExport/sqlexport.cpp | 329 ++ Plugins/SqlExport/sqlexport.h | 65 + Plugins/SqlExport/sqlexport.json | 7 + Plugins/SqlExport/sqlexport.qrc | 6 + Plugins/SqlExport/sqlexport_global.h | 12 + Plugins/SqlFormatterSimple/SqlFormatterSimple.pro | 28 + Plugins/SqlFormatterSimple/SqlFormatterSimple.ui | 51 + Plugins/SqlFormatterSimple/sqlformattersimple.json | 7 + Plugins/SqlFormatterSimple/sqlformattersimple.qrc | 5 + .../SqlFormatterSimple/sqlformattersimple_global.h | 12 + .../sqlformattersimpleplugin.cpp | 50 + .../SqlFormatterSimple/sqlformattersimpleplugin.h | 38 + Plugins/XmlExport/XmlExport.pro | 27 + Plugins/XmlExport/XmlExport.ui | 151 + Plugins/XmlExport/xmlexport.cpp | 468 ++ Plugins/XmlExport/xmlexport.h | 75 + Plugins/XmlExport/xmlexport.json | 7 + Plugins/XmlExport/xmlexport.qrc | 5 + Plugins/XmlExport/xmlexport_global.h | 12 + SQLiteStudio3/SQLiteStudio3.pro | 46 + .../CompletionHelperTest/CompletionHelperTest.pro | 21 + .../tst_completionhelpertest.cpp | 398 ++ .../DbVersionConverterTest.pro | 21 + .../tst_dbversionconvertertesttest.cpp | 119 + .../Tests/DsvFormatsTest/DsvFormatsTest.pro | 20 + .../DsvFormatsTest/tst_dsvformatstesttest.cpp | 73 + .../Tests/HashTablesTest/HashTablesTest.pro | 21 + .../HashTablesTest/tst_hashtablestesttest.cpp | 219 + SQLiteStudio3/Tests/ParserTest/ParserTest.pro | 20 + SQLiteStudio3/Tests/ParserTest/tst_parsertest.cpp | 357 ++ .../SelectResolverTest/SelectResolverTest.pro | 19 + .../SelectResolverTest/tst_selectresolvertest.cpp | 248 + .../Tests/TableModifierTest/TableModifierTest.pro | 19 + .../TableModifierTest/tst_tablemodifiertest.cpp | 266 + SQLiteStudio3/Tests/TestUtils/TestUtils.pro | 52 + .../Tests/TestUtils/collationmanagermock.cpp | 29 + .../Tests/TestUtils/collationmanagermock.h | 18 + SQLiteStudio3/Tests/TestUtils/configmock.cpp | 206 + SQLiteStudio3/Tests/TestUtils/configmock.h | 60 + SQLiteStudio3/Tests/TestUtils/dbattachermock.cpp | 35 + SQLiteStudio3/Tests/TestUtils/dbattachermock.h | 23 + SQLiteStudio3/Tests/TestUtils/dbmanagermock.cpp | 81 + SQLiteStudio3/Tests/TestUtils/dbmanagermock.h | 30 + SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.cpp | 8 + SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.h | 15 + .../Tests/TestUtils/functionmanagermock.cpp | 38 + .../Tests/TestUtils/functionmanagermock.h | 21 + SQLiteStudio3/Tests/TestUtils/hippomocks.h | 2361 +++++++++ SQLiteStudio3/Tests/TestUtils/mocks.cpp | 37 + SQLiteStudio3/Tests/TestUtils/mocks.h | 11 + .../Tests/TestUtils/pluginmanagermock.cpp | 162 + SQLiteStudio3/Tests/TestUtils/pluginmanagermock.h | 46 + SQLiteStudio3/Tests/TestUtils/test_common.pri | 6 + SQLiteStudio3/Tests/TestUtils/testutils_global.h | 12 + SQLiteStudio3/Tests/Tests.pro | 31 + SQLiteStudio3/Tests/testdirs.pri | 4 + .../UpdateSQLiteStudio.exe.manifest | 19 + .../UpdateSQLiteStudio/UpdateSQLiteStudio.pro | 39 + SQLiteStudio3/UpdateSQLiteStudio/main.cpp | 49 + .../UpdateSQLiteStudio/windows.manifest.autosave | 19 + SQLiteStudio3/UpdateSQLiteStudio/windows.rc | 3 + SQLiteStudio3/coreSQLiteStudio/TODO.txt | 66 + SQLiteStudio3/coreSQLiteStudio/committable.cpp | 41 + SQLiteStudio3/coreSQLiteStudio/committable.h | 27 + SQLiteStudio3/coreSQLiteStudio/common/bihash.h | 302 ++ SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h | 362 ++ SQLiteStudio3/coreSQLiteStudio/common/column.cpp | 38 + SQLiteStudio3/coreSQLiteStudio/common/column.h | 26 + SQLiteStudio3/coreSQLiteStudio/common/global.h | 66 + .../coreSQLiteStudio/common/memoryusage.cpp | 83 + .../coreSQLiteStudio/common/memoryusage.h | 6 + .../coreSQLiteStudio/common/nulldevice.cpp | 19 + SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h | 15 + SQLiteStudio3/coreSQLiteStudio/common/objectpool.h | 84 + .../coreSQLiteStudio/common/readwritelocker.cpp | 103 + .../coreSQLiteStudio/common/readwritelocker.h | 65 + SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h | 164 + SQLiteStudio3/coreSQLiteStudio/common/strhash.h | 182 + SQLiteStudio3/coreSQLiteStudio/common/table.cpp | 52 + SQLiteStudio3/coreSQLiteStudio/common/table.h | 32 + SQLiteStudio3/coreSQLiteStudio/common/unused.h | 6 + SQLiteStudio3/coreSQLiteStudio/common/utils.cpp | 855 ++++ SQLiteStudio3/coreSQLiteStudio/common/utils.h | 244 + .../coreSQLiteStudio/common/utils_sql.cpp | 552 ++ SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h | 71 + .../coreSQLiteStudio/completioncomparer.cpp | 439 ++ .../coreSQLiteStudio/completioncomparer.h | 74 + .../coreSQLiteStudio/completionhelper.cpp | 1425 ++++++ SQLiteStudio3/coreSQLiteStudio/completionhelper.h | 264 + SQLiteStudio3/coreSQLiteStudio/config_builder.h | 76 + .../config_builder/cfgcategory.cpp | 99 + .../coreSQLiteStudio/config_builder/cfgcategory.h | 53 + .../coreSQLiteStudio/config_builder/cfgentry.cpp | 189 + .../coreSQLiteStudio/config_builder/cfgentry.h | 123 + .../config_builder/cfglazyinitializer.cpp | 28 + .../config_builder/cfglazyinitializer.h | 23 + .../coreSQLiteStudio/config_builder/cfgmain.cpp | 120 + .../coreSQLiteStudio/config_builder/cfgmain.h | 51 + .../coreSQLiteStudio/coreSQLiteStudio.pro | 421 ++ .../coreSQLiteStudio/coreSQLiteStudio_global.h | 19 + .../coreSQLiteStudio/coresqlitestudio.qrc | 21 + SQLiteStudio3/coreSQLiteStudio/csvformat.cpp | 13 + SQLiteStudio3/coreSQLiteStudio/csvformat.h | 18 + SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp | 94 + SQLiteStudio3/coreSQLiteStudio/csvserializer.h | 15 + SQLiteStudio3/coreSQLiteStudio/datatype.cpp | 231 + SQLiteStudio3/coreSQLiteStudio/datatype.h | 72 + SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp | 879 ++++ SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h | 485 ++ SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h | 882 ++++ SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h | 1157 +++++ .../coreSQLiteStudio/db/asyncqueryrunner.cpp | 62 + .../coreSQLiteStudio/db/asyncqueryrunner.h | 126 + SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp | 20 + SQLiteStudio3/coreSQLiteStudio/db/attachguard.h | 24 + .../coreSQLiteStudio/db/chainexecutor.cpp | 200 + SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h | 359 ++ SQLiteStudio3/coreSQLiteStudio/db/db.cpp | 57 + SQLiteStudio3/coreSQLiteStudio/db/db.h | 831 ++++ SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h | 97 + .../coreSQLiteStudio/db/dbsqlite.h.autosave | 72 + SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp | 11 + SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h | 33 + SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp | 336 ++ SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h | 81 + .../coreSQLiteStudio/db/queryexecutor.cpp | 784 +++ SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h | 1369 +++++ .../queryexecutorsteps/queryexecutoraddrowids.cpp | 216 + .../db/queryexecutorsteps/queryexecutoraddrowids.h | 81 + .../queryexecutorsteps/queryexecutorattaches.cpp | 16 + .../db/queryexecutorsteps/queryexecutorattaches.h | 28 + .../queryexecutorsteps/queryexecutorcellsize.cpp | 99 + .../db/queryexecutorsteps/queryexecutorcellsize.h | 62 + .../db/queryexecutorsteps/queryexecutorcolumns.cpp | 256 + .../db/queryexecutorsteps/queryexecutorcolumns.h | 72 + .../queryexecutorcountresults.cpp | 22 + .../queryexecutorsteps/queryexecutorcountresults.h | 19 + .../queryexecutordatasources.cpp | 32 + .../queryexecutorsteps/queryexecutordatasources.h | 24 + .../queryexecutordetectschemaalter.cpp | 26 + .../queryexecutordetectschemaalter.h | 14 + .../db/queryexecutorsteps/queryexecutorexecute.cpp | 149 + .../db/queryexecutorsteps/queryexecutorexecute.h | 85 + .../queryexecutorexplainmode.cpp | 28 + .../queryexecutorsteps/queryexecutorexplainmode.h | 18 + .../db/queryexecutorsteps/queryexecutorlimit.cpp | 30 + .../db/queryexecutorsteps/queryexecutorlimit.h | 22 + .../db/queryexecutorsteps/queryexecutororder.cpp | 79 + .../db/queryexecutorsteps/queryexecutororder.h | 30 + .../queryexecutorsteps/queryexecutorparsequery.cpp | 48 + .../queryexecutorsteps/queryexecutorparsequery.h | 29 + .../queryexecutorreplaceviews.cpp | 113 + .../queryexecutorsteps/queryexecutorreplaceviews.h | 110 + .../db/queryexecutorsteps/queryexecutorstep.cpp | 63 + .../db/queryexecutorsteps/queryexecutorstep.h | 158 + .../queryexecutorsteps/queryexecutorvaluesmode.cpp | 26 + .../queryexecutorsteps/queryexecutorvaluesmode.h | 14 + .../queryexecutorwrapdistinctresults.cpp | 42 + .../queryexecutorwrapdistinctresults.h | 33 + .../coreSQLiteStudio/db/sqlerrorcodes.cpp | 13 + SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h | 39 + .../coreSQLiteStudio/db/sqlerrorresults.cpp | 55 + .../coreSQLiteStudio/db/sqlerrorresults.h | 48 + SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp | 142 + SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h | 323 ++ .../coreSQLiteStudio/db/sqlresultsrow.cpp | 42 + SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h | 102 + .../coreSQLiteStudio/db/stdsqlite3driver.h | 83 + SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp | 11 + SQLiteStudio3/coreSQLiteStudio/dbattacher.h | 99 + .../coreSQLiteStudio/dbobjectorganizer.cpp | 777 +++ SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h | 152 + SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h | 12 + .../coreSQLiteStudio/dbversionconverter.cpp | 1253 +++++ .../coreSQLiteStudio/dbversionconverter.h | 133 + SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp | 76 + SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h | 31 + SQLiteStudio3/coreSQLiteStudio/dialect.h | 10 + .../coreSQLiteStudio/diff/diff_match_patch.cpp | 2112 ++++++++ .../coreSQLiteStudio/diff/diff_match_patch.h | 631 +++ SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp | 73 + SQLiteStudio3/coreSQLiteStudio/expectedtoken.h | 107 + SQLiteStudio3/coreSQLiteStudio/exportworker.cpp | 516 ++ SQLiteStudio3/coreSQLiteStudio/exportworker.h | 60 + .../coreSQLiteStudio/impl/dbattacherimpl.cpp | 178 + .../coreSQLiteStudio/impl/dbattacherimpl.h | 94 + SQLiteStudio3/coreSQLiteStudio/importworker.cpp | 171 + SQLiteStudio3/coreSQLiteStudio/importworker.h | 44 + SQLiteStudio3/coreSQLiteStudio/interruptable.h | 23 + .../coreSQLiteStudio/licenses/diff_match.txt | 18 + .../coreSQLiteStudio/licenses/fugue_icons.txt | 80 + SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt | 675 +++ SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt | 502 ++ .../licenses/sqlitestudio_license.txt | 15 + SQLiteStudio3/coreSQLiteStudio/log.cpp | 47 + SQLiteStudio3/coreSQLiteStudio/log.h | 16 + .../parser/ast/sqlitealtertable.cpp | 128 + .../coreSQLiteStudio/parser/ast/sqlitealtertable.h | 46 + .../coreSQLiteStudio/parser/ast/sqliteanalyze.cpp | 80 + .../coreSQLiteStudio/parser/ast/sqliteanalyze.h | 29 + .../coreSQLiteStudio/parser/ast/sqliteattach.cpp | 63 + .../coreSQLiteStudio/parser/ast/sqliteattach.h | 28 + .../parser/ast/sqlitebegintrans.cpp | 71 + .../coreSQLiteStudio/parser/ast/sqlitebegintrans.h | 38 + .../parser/ast/sqlitecolumntype.cpp | 88 + .../coreSQLiteStudio/parser/ast/sqlitecolumntype.h | 30 + .../parser/ast/sqlitecommittrans.cpp | 48 + .../parser/ast/sqlitecommittrans.h | 25 + .../parser/ast/sqliteconflictalgo.cpp | 37 + .../parser/ast/sqliteconflictalgo.h | 20 + .../coreSQLiteStudio/parser/ast/sqlitecopy.cpp | 92 + .../coreSQLiteStudio/parser/ast/sqlitecopy.h | 32 + .../parser/ast/sqlitecreateindex.cpp | 190 + .../parser/ast/sqlitecreateindex.h | 50 + .../parser/ast/sqlitecreatetable.cpp | 771 +++ .../parser/ast/sqlitecreatetable.h | 205 + .../parser/ast/sqlitecreatetrigger.cpp | 381 ++ .../parser/ast/sqlitecreatetrigger.h | 96 + .../parser/ast/sqlitecreateview.cpp | 120 + .../coreSQLiteStudio/parser/ast/sqlitecreateview.h | 35 + .../parser/ast/sqlitecreatevirtualtable.cpp | 120 + .../parser/ast/sqlitecreatevirtualtable.h | 42 + .../parser/ast/sqlitedeferrable.cpp | 54 + .../coreSQLiteStudio/parser/ast/sqlitedeferrable.h | 27 + .../coreSQLiteStudio/parser/ast/sqlitedelete.cpp | 137 + .../coreSQLiteStudio/parser/ast/sqlitedelete.h | 45 + .../coreSQLiteStudio/parser/ast/sqlitedetach.cpp | 48 + .../coreSQLiteStudio/parser/ast/sqlitedetach.h | 27 + .../parser/ast/sqlitedropindex.cpp | 78 + .../coreSQLiteStudio/parser/ast/sqlitedropindex.h | 29 + .../parser/ast/sqlitedroptable.cpp | 86 + .../coreSQLiteStudio/parser/ast/sqlitedroptable.h | 31 + .../parser/ast/sqlitedroptrigger.cpp | 79 + .../parser/ast/sqlitedroptrigger.h | 29 + .../coreSQLiteStudio/parser/ast/sqlitedropview.cpp | 85 + .../coreSQLiteStudio/parser/ast/sqlitedropview.h | 29 + .../parser/ast/sqliteemptyquery.cpp | 27 + .../coreSQLiteStudio/parser/ast/sqliteemptyquery.h | 19 + .../coreSQLiteStudio/parser/ast/sqliteexpr.cpp | 644 +++ .../coreSQLiteStudio/parser/ast/sqliteexpr.h | 144 + .../parser/ast/sqliteforeignkey.cpp | 187 + .../coreSQLiteStudio/parser/ast/sqliteforeignkey.h | 75 + .../parser/ast/sqliteindexedcolumn.cpp | 52 + .../parser/ast/sqliteindexedcolumn.h | 30 + .../coreSQLiteStudio/parser/ast/sqliteinsert.cpp | 214 + .../coreSQLiteStudio/parser/ast/sqliteinsert.h | 57 + .../coreSQLiteStudio/parser/ast/sqlitelimit.cpp | 79 + .../coreSQLiteStudio/parser/ast/sqlitelimit.h | 31 + .../coreSQLiteStudio/parser/ast/sqliteorderby.cpp | 41 + .../coreSQLiteStudio/parser/ast/sqliteorderby.h | 28 + .../coreSQLiteStudio/parser/ast/sqlitepragma.cpp | 109 + .../coreSQLiteStudio/parser/ast/sqlitepragma.h | 41 + .../coreSQLiteStudio/parser/ast/sqlitequery.cpp | 51 + .../coreSQLiteStudio/parser/ast/sqlitequery.h | 30 + .../parser/ast/sqlitequerytype.cpp | 66 + .../coreSQLiteStudio/parser/ast/sqlitequerytype.h | 41 + .../coreSQLiteStudio/parser/ast/sqliteraise.cpp | 70 + .../coreSQLiteStudio/parser/ast/sqliteraise.h | 38 + .../coreSQLiteStudio/parser/ast/sqlitereindex.cpp | 82 + .../coreSQLiteStudio/parser/ast/sqlitereindex.h | 31 + .../coreSQLiteStudio/parser/ast/sqliterelease.cpp | 39 + .../coreSQLiteStudio/parser/ast/sqliterelease.h | 26 + .../coreSQLiteStudio/parser/ast/sqliterollback.cpp | 57 + .../coreSQLiteStudio/parser/ast/sqliterollback.h | 28 + .../parser/ast/sqlitesavepoint.cpp | 32 + .../coreSQLiteStudio/parser/ast/sqlitesavepoint.h | 25 + .../coreSQLiteStudio/parser/ast/sqliteselect.cpp | 783 +++ .../coreSQLiteStudio/parser/ast/sqliteselect.h | 228 + .../parser/ast/sqlitesortorder.cpp | 25 + .../coreSQLiteStudio/parser/ast/sqlitesortorder.h | 17 + .../parser/ast/sqlitestatement.cpp | 553 ++ .../coreSQLiteStudio/parser/ast/sqlitestatement.h | 339 ++ .../parser/ast/sqlitetablerelatedddl.h | 14 + .../coreSQLiteStudio/parser/ast/sqliteupdate.cpp | 207 + .../coreSQLiteStudio/parser/ast/sqliteupdate.h | 51 + .../coreSQLiteStudio/parser/ast/sqlitevacuum.cpp | 56 + .../coreSQLiteStudio/parser/ast/sqlitevacuum.h | 28 + .../coreSQLiteStudio/parser/ast/sqlitewith.cpp | 87 + .../coreSQLiteStudio/parser/ast/sqlitewith.h | 45 + SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp | 324 ++ SQLiteStudio3/coreSQLiteStudio/parser/keywords.h | 123 + SQLiteStudio3/coreSQLiteStudio/parser/lempar.c | 1021 ++++ SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp | 314 ++ SQLiteStudio3/coreSQLiteStudio/parser/lexer.h | 254 + .../coreSQLiteStudio/parser/lexer_low_lev.cpp | 470 ++ .../coreSQLiteStudio/parser/lexer_low_lev.h | 27 + SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp | 323 ++ SQLiteStudio3/coreSQLiteStudio/parser/parser.h | 360 ++ .../parser/parser_helper_stubs.cpp | 45 + .../coreSQLiteStudio/parser/parser_helper_stubs.h | 118 + .../coreSQLiteStudio/parser/parsercontext.cpp | 184 + .../coreSQLiteStudio/parser/parsercontext.h | 289 ++ .../coreSQLiteStudio/parser/parsererror.cpp | 44 + .../coreSQLiteStudio/parser/parsererror.h | 80 + SQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh | 8 + .../coreSQLiteStudio/parser/sqlite2_parse.cpp | 4650 +++++++++++++++++ .../coreSQLiteStudio/parser/sqlite2_parse.h | 146 + .../coreSQLiteStudio/parser/sqlite2_parse.y | 2068 ++++++++ .../coreSQLiteStudio/parser/sqlite3_parse.cpp | 5262 ++++++++++++++++++++ .../coreSQLiteStudio/parser/sqlite3_parse.h | 167 + .../coreSQLiteStudio/parser/sqlite3_parse.y | 2406 +++++++++ .../parser/statementtokenbuilder.cpp | 206 + .../parser/statementtokenbuilder.h | 290 ++ SQLiteStudio3/coreSQLiteStudio/parser/token.cpp | 621 +++ SQLiteStudio3/coreSQLiteStudio/parser/token.h | 733 +++ SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp | 35 + SQLiteStudio3/coreSQLiteStudio/pluginloader.h | 35 + .../coreSQLiteStudio/plugins/builtinplugin.cpp | 58 + .../coreSQLiteStudio/plugins/builtinplugin.h | 147 + .../coreSQLiteStudio/plugins/codeformatterplugin.h | 16 + .../plugins/confignotifiableplugin.h | 14 + SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h | 73 + .../coreSQLiteStudio/plugins/dbpluginsqlite3.cpp | 47 + .../coreSQLiteStudio/plugins/dbpluginsqlite3.h | 24 + .../coreSQLiteStudio/plugins/exportplugin.h | 372 ++ .../plugins/generalpurposeplugin.h | 20 + .../plugins/genericexportplugin.cpp | 160 + .../coreSQLiteStudio/plugins/genericexportplugin.h | 56 + .../coreSQLiteStudio/plugins/genericplugin.cpp | 67 + .../coreSQLiteStudio/plugins/genericplugin.h | 126 + .../coreSQLiteStudio/plugins/importplugin.h | 132 + SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h | 182 + .../plugins/pluginsymbolresolver.cpp | 45 + .../plugins/pluginsymbolresolver.h | 24 + .../coreSQLiteStudio/plugins/plugintype.cpp | 52 + .../coreSQLiteStudio/plugins/plugintype.h | 63 + .../coreSQLiteStudio/plugins/populateconstant.cpp | 48 + .../coreSQLiteStudio/plugins/populateconstant.h | 44 + .../coreSQLiteStudio/plugins/populateconstant.ui | 37 + .../plugins/populatedictionary.cpp | 94 + .../coreSQLiteStudio/plugins/populatedictionary.h | 52 + .../coreSQLiteStudio/plugins/populatedictionary.ui | 123 + .../coreSQLiteStudio/plugins/populateplugin.h | 70 + .../coreSQLiteStudio/plugins/populaterandom.cpp | 55 + .../coreSQLiteStudio/plugins/populaterandom.h | 47 + .../coreSQLiteStudio/plugins/populaterandom.ui | 106 + .../plugins/populaterandomtext.cpp | 91 + .../coreSQLiteStudio/plugins/populaterandomtext.h | 52 + .../coreSQLiteStudio/plugins/populaterandomtext.ui | 181 + .../coreSQLiteStudio/plugins/populatescript.cpp | 125 + .../coreSQLiteStudio/plugins/populatescript.h | 63 + .../coreSQLiteStudio/plugins/populatescript.ui | 112 + .../coreSQLiteStudio/plugins/populatesequence.cpp | 53 + .../coreSQLiteStudio/plugins/populatesequence.h | 47 + .../coreSQLiteStudio/plugins/populatesequence.ui | 64 + .../coreSQLiteStudio/plugins/scriptingplugin.h | 50 + .../coreSQLiteStudio/plugins/scriptingqt.cpp | 276 + .../coreSQLiteStudio/plugins/scriptingqt.h | 72 + .../coreSQLiteStudio/plugins/scriptingqt.png | Bin 0 -> 1750 bytes .../plugins/scriptingqtdbproxy.cpp | 145 + .../coreSQLiteStudio/plugins/scriptingqtdbproxy.h | 44 + .../coreSQLiteStudio/plugins/scriptingsql.cpp | 146 + .../coreSQLiteStudio/plugins/scriptingsql.h | 46 + .../coreSQLiteStudio/plugins/scriptingsql.png | Bin 0 -> 376 bytes .../plugins/sqlformatterplugin.cpp | 29 + .../coreSQLiteStudio/plugins/sqlformatterplugin.h | 16 + .../coreSQLiteStudio/plugins/uiconfiguredplugin.h | 56 + .../coreSQLiteStudio/pluginservicebase.cpp | 21 + SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h | 95 + SQLiteStudio3/coreSQLiteStudio/populateworker.cpp | 110 + SQLiteStudio3/coreSQLiteStudio/populateworker.h | 41 + SQLiteStudio3/coreSQLiteStudio/qio.cpp | 5 + SQLiteStudio3/coreSQLiteStudio/qio.h | 33 + SQLiteStudio3/coreSQLiteStudio/querymodel.cpp | 63 + SQLiteStudio3/coreSQLiteStudio/querymodel.h | 42 + SQLiteStudio3/coreSQLiteStudio/returncode.cpp | 51 + SQLiteStudio3/coreSQLiteStudio/returncode.h | 26 + SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp | 1151 +++++ SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h | 294 ++ SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp | 37 + SQLiteStudio3/coreSQLiteStudio/rsa/Key.h | 59 + SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp | 37 + SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h | 58 + .../coreSQLiteStudio/rsa/PrimeGenerator.cpp | 198 + .../coreSQLiteStudio/rsa/PrimeGenerator.h | 71 + SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp | 394 ++ SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h | 130 + SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp | 910 ++++ SQLiteStudio3/coreSQLiteStudio/schemaresolver.h | 220 + SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp | 673 +++ SQLiteStudio3/coreSQLiteStudio/selectresolver.h | 299 ++ .../coreSQLiteStudio/services/bugreporter.cpp | 202 + .../coreSQLiteStudio/services/bugreporter.h | 62 + .../coreSQLiteStudio/services/codeformatter.cpp | 91 + .../coreSQLiteStudio/services/codeformatter.h | 30 + .../coreSQLiteStudio/services/collationmanager.h | 41 + SQLiteStudio3/coreSQLiteStudio/services/config.cpp | 9 + SQLiteStudio3/coreSQLiteStudio/services/config.h | 178 + .../coreSQLiteStudio/services/dbmanager.cpp | 17 + .../coreSQLiteStudio/services/dbmanager.h | 288 ++ .../coreSQLiteStudio/services/exportmanager.cpp | 283 ++ .../coreSQLiteStudio/services/exportmanager.h | 234 + .../services/extralicensemanager.cpp | 28 + .../services/extralicensemanager.h | 21 + .../coreSQLiteStudio/services/functionmanager.cpp | 39 + .../coreSQLiteStudio/services/functionmanager.h | 74 + .../services/impl/collationmanagerimpl.cpp | 125 + .../services/impl/collationmanagerimpl.h | 36 + .../coreSQLiteStudio/services/impl/configimpl.cpp | 770 +++ .../coreSQLiteStudio/services/impl/configimpl.h | 127 + .../services/impl/dbmanagerimpl.cpp | 524 ++ .../coreSQLiteStudio/services/impl/dbmanagerimpl.h | 183 + .../services/impl/functionmanagerimpl.cpp | 694 +++ .../services/impl/functionmanagerimpl.h | 96 + .../services/impl/pluginmanagerimpl.cpp | 820 +++ .../services/impl/pluginmanagerimpl.h | 321 ++ .../coreSQLiteStudio/services/importmanager.cpp | 104 + .../coreSQLiteStudio/services/importmanager.h | 85 + .../coreSQLiteStudio/services/notifymanager.cpp | 85 + .../coreSQLiteStudio/services/notifymanager.h | 58 + .../coreSQLiteStudio/services/pluginmanager.h | 528 ++ .../coreSQLiteStudio/services/populatemanager.cpp | 93 + .../coreSQLiteStudio/services/populatemanager.h | 48 + .../coreSQLiteStudio/services/updatemanager.cpp | 1058 ++++ .../coreSQLiteStudio/services/updatemanager.h | 137 + SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp | 42 + SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h | 17 + SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp | 390 ++ SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h | 258 + SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp | 695 +++ SQLiteStudio3/coreSQLiteStudio/tablemodifier.h | 114 + SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp | 103 + SQLiteStudio3/coreSQLiteStudio/tsvserializer.h | 20 + SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp | 127 + SQLiteStudio3/coreSQLiteStudio/viewmodifier.h | 53 + SQLiteStudio3/create_linux_portable.sh | 152 + SQLiteStudio3/create_macosx_bundle.sh | 88 + SQLiteStudio3/create_source_dist.sh | 32 + SQLiteStudio3/create_win32_portable.bat | 119 + SQLiteStudio3/dirs.pri | 37 + SQLiteStudio3/docs/sqlitestudio3_docs.cfg | 2280 +++++++++ SQLiteStudio3/docs/sqlitestudio_logo.png | Bin 0 -> 9302 bytes SQLiteStudio3/guiSQLiteStudio/actionentry.cpp | 42 + SQLiteStudio3/guiSQLiteStudio/actionentry.h | 30 + .../guiSQLiteStudio/common/colorbutton.cpp | 39 + SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h | 30 + .../guiSQLiteStudio/common/configcombobox.cpp | 16 + .../guiSQLiteStudio/common/configcombobox.h | 34 + .../guiSQLiteStudio/common/configradiobutton.cpp | 36 + .../guiSQLiteStudio/common/configradiobutton.h | 46 + .../guiSQLiteStudio/common/datawidgetmapper.cpp | 138 + .../guiSQLiteStudio/common/datawidgetmapper.h | 54 + SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp | 30 + SQLiteStudio3/guiSQLiteStudio/common/extaction.h | 20 + .../guiSQLiteStudio/common/extactioncontainer.cpp | 260 + .../guiSQLiteStudio/common/extactioncontainer.h | 254 + .../common/extactionmanagementnotifier.cpp | 19 + .../common/extactionmanagementnotifier.h | 30 + .../guiSQLiteStudio/common/extactionprototype.cpp | 65 + .../guiSQLiteStudio/common/extactionprototype.h | 44 + .../guiSQLiteStudio/common/extlineedit.cpp | 118 + SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h | 45 + SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp | 98 + SQLiteStudio3/guiSQLiteStudio/common/fileedit.h | 52 + SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp | 68 + SQLiteStudio3/guiSQLiteStudio/common/fontedit.h | 43 + SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui | 35 + .../guiSQLiteStudio/common/intvalidator.cpp | 38 + .../guiSQLiteStudio/common/intvalidator.h | 25 + .../guiSQLiteStudio/common/numericspinbox.cpp | 152 + .../guiSQLiteStudio/common/numericspinbox.h | 57 + .../guiSQLiteStudio/common/tablewidget.cpp | 49 + SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h | 23 + .../guiSQLiteStudio/common/userinputfilter.cpp | 33 + .../guiSQLiteStudio/common/userinputfilter.h | 31 + .../common/verifiablewizardpage.cpp | 20 + .../guiSQLiteStudio/common/verifiablewizardpage.h | 22 + .../guiSQLiteStudio/common/widgetcover.cpp | 209 + SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h | 72 + .../common/widgetstateindicator.cpp | 412 ++ .../guiSQLiteStudio/common/widgetstateindicator.h | 99 + .../completer/completeritemdelegate.cpp | 133 + .../completer/completeritemdelegate.h | 31 + .../guiSQLiteStudio/completer/completermodel.cpp | 170 + .../guiSQLiteStudio/completer/completermodel.h | 50 + .../guiSQLiteStudio/completer/completerview.cpp | 70 + .../guiSQLiteStudio/completer/completerview.h | 30 + .../guiSQLiteStudio/completer/completerwindow.cpp | 229 + .../guiSQLiteStudio/completer/completerwindow.h | 64 + .../guiSQLiteStudio/completer/completerwindow.ui | 47 + SQLiteStudio3/guiSQLiteStudio/configmapper.cpp | 652 +++ SQLiteStudio3/guiSQLiteStudio/configmapper.h | 121 + SQLiteStudio3/guiSQLiteStudio/configuiplugin.h | 24 + .../configwidgets/combodatawidget.cpp | 63 + .../configwidgets/combodatawidget.h | 35 + .../configwidgets/listtostringlisthash.cpp | 67 + .../configwidgets/listtostringlisthash.h | 22 + .../configwidgets/styleconfigwidget.cpp | 54 + .../configwidgets/styleconfigwidget.h | 20 + .../constraints/columncheckpanel.cpp | 56 + .../guiSQLiteStudio/constraints/columncheckpanel.h | 27 + .../constraints/columncollatepanel.cpp | 107 + .../constraints/columncollatepanel.h | 40 + .../constraints/columncollatepanel.ui | 69 + .../constraints/columndefaultpanel.cpp | 204 + .../constraints/columndefaultpanel.h | 41 + .../constraints/columndefaultpanel.ui | 56 + .../constraints/columnforeignkeypanel.cpp | 266 + .../constraints/columnforeignkeypanel.h | 48 + .../constraints/columnforeignkeypanel.ui | 147 + .../constraints/columnnotnullpanel.cpp | 13 + .../constraints/columnnotnullpanel.h | 17 + .../constraints/columnprimarykeypanel.cpp | 127 + .../constraints/columnprimarykeypanel.h | 36 + .../constraints/columnprimarykeypanel.ui | 105 + .../constraints/columnuniqueandnotnullpanel.cpp | 99 + .../constraints/columnuniqueandnotnullpanel.h | 37 + .../constraints/columnuniqueandnotnullpanel.ui | 72 + .../constraints/columnuniquepanel.cpp | 13 + .../constraints/columnuniquepanel.h | 22 + .../constraints/constraintcheckpanel.cpp | 175 + .../constraints/constraintcheckpanel.h | 49 + .../constraints/constraintcheckpanel.ui | 72 + .../constraints/constraintpanel.cpp | 96 + .../guiSQLiteStudio/constraints/constraintpanel.h | 79 + .../constraints/tablecheckpanel.cpp | 56 + .../guiSQLiteStudio/constraints/tablecheckpanel.h | 27 + .../constraints/tableforeignkeypanel.cpp | 418 ++ .../constraints/tableforeignkeypanel.h | 57 + .../constraints/tableforeignkeypanel.ui | 215 + .../constraints/tablepkanduniquepanel.cpp | 299 ++ .../constraints/tablepkanduniquepanel.h | 63 + .../constraints/tablepkanduniquepanel.ui | 228 + .../constraints/tableprimarykeypanel.cpp | 83 + .../constraints/tableprimarykeypanel.h | 23 + .../constraints/tableprimarykeypanel.ui | 102 + .../constraints/tableuniquepanel.cpp | 21 + .../guiSQLiteStudio/constraints/tableuniquepanel.h | 16 + .../guiSQLiteStudio/customconfigwidgetplugin.h | 21 + .../guiSQLiteStudio/datagrid/sqlqueryitem.cpp | 460 ++ .../guiSQLiteStudio/datagrid/sqlqueryitem.h | 96 + .../datagrid/sqlqueryitemdelegate.cpp | 70 + .../datagrid/sqlqueryitemdelegate.h | 23 + .../guiSQLiteStudio/datagrid/sqlquerymodel.cpp | 1519 ++++++ .../guiSQLiteStudio/datagrid/sqlquerymodel.h | 444 ++ .../datagrid/sqlquerymodelcolumn.cpp | 465 ++ .../guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h | 177 + .../datagrid/sqlqueryrownummodel.cpp | 63 + .../guiSQLiteStudio/datagrid/sqlqueryrownummodel.h | 27 + .../guiSQLiteStudio/datagrid/sqlqueryview.cpp | 454 ++ .../guiSQLiteStudio/datagrid/sqlqueryview.h | 130 + .../guiSQLiteStudio/datagrid/sqltablemodel.cpp | 332 ++ .../guiSQLiteStudio/datagrid/sqltablemodel.h | 50 + SQLiteStudio3/guiSQLiteStudio/dataview.cpp | 836 ++++ SQLiteStudio3/guiSQLiteStudio/dataview.h | 199 + SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp | 184 + SQLiteStudio3/guiSQLiteStudio/dblistmodel.h | 66 + SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp | 309 ++ SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h | 67 + SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp | 120 + SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h | 59 + SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp | 1557 ++++++ SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h | 205 + SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui | 90 + .../guiSQLiteStudio/dbtree/dbtreeitem.cpp | 332 ++ SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h | 112 + .../guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp | 164 + .../guiSQLiteStudio/dbtree/dbtreeitemdelegate.h | 28 + .../guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp | 74 + .../guiSQLiteStudio/dbtree/dbtreeitemfactory.h | 26 + .../guiSQLiteStudio/dbtree/dbtreemodel.cpp | 1222 +++++ SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h | 135 + .../guiSQLiteStudio/dbtree/dbtreeview.cpp | 255 + SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h | 57 + SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp | 79 + SQLiteStudio3/guiSQLiteStudio/debugconsole.h | 44 + SQLiteStudio3/guiSQLiteStudio/debugconsole.ui | 73 + .../guiSQLiteStudio/dialogs/aboutdialog.cpp | 94 + .../guiSQLiteStudio/dialogs/aboutdialog.h | 37 + .../guiSQLiteStudio/dialogs/aboutdialog.ui | 109 + .../guiSQLiteStudio/dialogs/bugdialog.cpp | 220 + SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h | 42 + SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui | 208 + .../dialogs/bugreportlogindialog.cpp | 94 + .../guiSQLiteStudio/dialogs/bugreportlogindialog.h | 40 + .../dialogs/bugreportlogindialog.ui | 132 + .../guiSQLiteStudio/dialogs/columndialog.cpp | 616 +++ .../guiSQLiteStudio/dialogs/columndialog.h | 113 + .../guiSQLiteStudio/dialogs/columndialog.ui | 348 ++ .../dialogs/columndialogconstraintsmodel.cpp | 335 ++ .../dialogs/columndialogconstraintsmodel.h | 58 + .../guiSQLiteStudio/dialogs/configdialog.cpp | 1529 ++++++ .../guiSQLiteStudio/dialogs/configdialog.h | 141 + .../guiSQLiteStudio/dialogs/configdialog.ui | 1923 +++++++ .../guiSQLiteStudio/dialogs/constraintdialog.cpp | 213 + .../guiSQLiteStudio/dialogs/constraintdialog.h | 79 + .../guiSQLiteStudio/dialogs/constraintdialog.ui | 113 + .../guiSQLiteStudio/dialogs/dbconverterdialog.cpp | 219 + .../guiSQLiteStudio/dialogs/dbconverterdialog.h | 52 + .../guiSQLiteStudio/dialogs/dbconverterdialog.ui | 144 + SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp | 590 +++ SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h | 89 + SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui | 236 + .../guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp | 58 + .../guiSQLiteStudio/dialogs/ddlpreviewdialog.h | 35 + .../guiSQLiteStudio/dialogs/ddlpreviewdialog.ui | 106 + .../dialogs/errorsconfirmdialog.cpp | 47 + .../guiSQLiteStudio/dialogs/errorsconfirmdialog.h | 28 + .../guiSQLiteStudio/dialogs/errorsconfirmdialog.ui | 85 + .../guiSQLiteStudio/dialogs/exportdialog.cpp | 737 +++ .../guiSQLiteStudio/dialogs/exportdialog.h | 105 + .../guiSQLiteStudio/dialogs/exportdialog.ui | 438 ++ .../guiSQLiteStudio/dialogs/importdialog.cpp | 376 ++ .../guiSQLiteStudio/dialogs/importdialog.h | 69 + .../guiSQLiteStudio/dialogs/importdialog.ui | 230 + .../guiSQLiteStudio/dialogs/indexdialog.cpp | 468 ++ .../guiSQLiteStudio/dialogs/indexdialog.h | 72 + .../guiSQLiteStudio/dialogs/indexdialog.ui | 195 + .../guiSQLiteStudio/dialogs/messagelistdialog.cpp | 97 + .../guiSQLiteStudio/dialogs/messagelistdialog.h | 37 + .../guiSQLiteStudio/dialogs/messagelistdialog.ui | 84 + .../dialogs/newconstraintdialog.cpp | 275 + .../guiSQLiteStudio/dialogs/newconstraintdialog.h | 65 + .../guiSQLiteStudio/dialogs/newconstraintdialog.ui | 75 + .../guiSQLiteStudio/dialogs/newversiondialog.cpp | 68 + .../guiSQLiteStudio/dialogs/newversiondialog.h | 34 + .../guiSQLiteStudio/dialogs/newversiondialog.ui | 144 + .../dialogs/populateconfigdialog.cpp | 119 + .../guiSQLiteStudio/dialogs/populateconfigdialog.h | 47 + .../dialogs/populateconfigdialog.ui | 99 + .../guiSQLiteStudio/dialogs/populatedialog.cpp | 332 ++ .../guiSQLiteStudio/dialogs/populatedialog.h | 76 + .../guiSQLiteStudio/dialogs/populatedialog.ui | 158 + .../guiSQLiteStudio/dialogs/quitconfirmdialog.cpp | 42 + .../guiSQLiteStudio/dialogs/quitconfirmdialog.h | 28 + .../guiSQLiteStudio/dialogs/quitconfirmdialog.ui | 83 + .../guiSQLiteStudio/dialogs/searchtextdialog.cpp | 75 + .../guiSQLiteStudio/dialogs/searchtextdialog.h | 39 + .../guiSQLiteStudio/dialogs/searchtextdialog.ui | 153 + .../guiSQLiteStudio/dialogs/sortdialog.cpp | 248 + SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h | 60 + .../guiSQLiteStudio/dialogs/sortdialog.ui | 112 + .../dialogs/triggercolumnsdialog.cpp | 52 + .../guiSQLiteStudio/dialogs/triggercolumnsdialog.h | 33 + .../dialogs/triggercolumnsdialog.ui | 117 + .../guiSQLiteStudio/dialogs/triggerdialog.cpp | 413 ++ .../guiSQLiteStudio/dialogs/triggerdialog.h | 62 + .../guiSQLiteStudio/dialogs/triggerdialog.ui | 215 + .../dialogs/versionconvertsummarydialog.cpp | 31 + .../dialogs/versionconvertsummarydialog.h | 28 + .../dialogs/versionconvertsummarydialog.ui | 91 + SQLiteStudio3/guiSQLiteStudio/formmanager.cpp | 185 + SQLiteStudio3/guiSQLiteStudio/formmanager.h | 43 + .../guiSQLiteStudio/forms/sqlformatterplugin.ui | 46 + SQLiteStudio3/guiSQLiteStudio/formview.cpp | 274 + SQLiteStudio3/guiSQLiteStudio/formview.h | 114 + SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro | 364 ++ .../guiSQLiteStudio/guiSQLiteStudio_global.h | 12 + SQLiteStudio3/guiSQLiteStudio/icon.cpp | 385 ++ SQLiteStudio3/guiSQLiteStudio/icon.h | 104 + SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp | 164 + SQLiteStudio3/guiSQLiteStudio/iconmanager.h | 275 + SQLiteStudio3/guiSQLiteStudio/icons.qrc | 194 + SQLiteStudio3/guiSQLiteStudio/img/abort24.png | Bin 0 -> 1081 bytes SQLiteStudio3/guiSQLiteStudio/img/act_abort.png | Bin 0 -> 544 bytes SQLiteStudio3/guiSQLiteStudio/img/act_clear.png | Bin 0 -> 784 bytes SQLiteStudio3/guiSQLiteStudio/img/act_copy.png | Bin 0 -> 606 bytes SQLiteStudio3/guiSQLiteStudio/img/act_cut.png | Bin 0 -> 690 bytes SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png | Bin 0 -> 529 bytes SQLiteStudio3/guiSQLiteStudio/img/act_delete.png | Bin 0 -> 663 bytes SQLiteStudio3/guiSQLiteStudio/img/act_paste.png | Bin 0 -> 685 bytes SQLiteStudio3/guiSQLiteStudio/img/act_redo.png | Bin 0 -> 613 bytes SQLiteStudio3/guiSQLiteStudio/img/act_search.png | Bin 0 -> 781 bytes .../guiSQLiteStudio/img/act_select_all.png | Bin 0 -> 327 bytes SQLiteStudio3/guiSQLiteStudio/img/act_undo.png | Bin 0 -> 632 bytes SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png | Bin 0 -> 591 bytes .../guiSQLiteStudio/img/apply_filter_re.png | Bin 0 -> 589 bytes .../guiSQLiteStudio/img/apply_filter_sql.png | Bin 0 -> 601 bytes .../guiSQLiteStudio/img/apply_filter_txt.png | Bin 0 -> 566 bytes SQLiteStudio3/guiSQLiteStudio/img/bug.png | Bin 0 -> 704 bytes SQLiteStudio3/guiSQLiteStudio/img/bug_list.png | Bin 0 -> 793 bytes SQLiteStudio3/guiSQLiteStudio/img/check.png | Bin 0 -> 650 bytes .../guiSQLiteStudio/img/clear_history.png | Bin 0 -> 658 bytes .../guiSQLiteStudio/img/clear_lineedit.png | Bin 0 -> 493 bytes SQLiteStudio3/guiSQLiteStudio/img/close.png | Bin 0 -> 544 bytes SQLiteStudio3/guiSQLiteStudio/img/collation.png | Bin 0 -> 599 bytes SQLiteStudio3/guiSQLiteStudio/img/column.png | Bin 0 -> 580 bytes .../guiSQLiteStudio/img/column_constraint.png | Bin 0 -> 814 bytes SQLiteStudio3/guiSQLiteStudio/img/columns.png | Bin 0 -> 707 bytes SQLiteStudio3/guiSQLiteStudio/img/commit.png | Bin 0 -> 605 bytes SQLiteStudio3/guiSQLiteStudio/img/complete.png | Bin 0 -> 671 bytes .../guiSQLiteStudio/img/completer_blob.png | Bin 0 -> 668 bytes .../guiSQLiteStudio/img/completer_no_value.png | Bin 0 -> 404 bytes .../guiSQLiteStudio/img/completer_number.png | Bin 0 -> 276 bytes .../guiSQLiteStudio/img/completer_operator.png | Bin 0 -> 308 bytes .../guiSQLiteStudio/img/completer_other.png | Bin 0 -> 415 bytes .../guiSQLiteStudio/img/completer_pragma.png | Bin 0 -> 845 bytes .../guiSQLiteStudio/img/completer_string.png | Bin 0 -> 524 bytes .../guiSQLiteStudio/img/config_colors.png | Bin 0 -> 865 bytes .../guiSQLiteStudio/img/config_data_editors.png | Bin 0 -> 359 bytes SQLiteStudio3/guiSQLiteStudio/img/config_font.png | Bin 0 -> 459 bytes .../guiSQLiteStudio/img/config_general.png | Bin 0 -> 435 bytes .../guiSQLiteStudio/img/config_look_and_feel.png | Bin 0 -> 538 bytes SQLiteStudio3/guiSQLiteStudio/img/config_style.png | Bin 0 -> 927 bytes SQLiteStudio3/guiSQLiteStudio/img/configure.png | Bin 0 -> 699 bytes .../guiSQLiteStudio/img/configure_constraint.png | Bin 0 -> 297 bytes SQLiteStudio3/guiSQLiteStudio/img/convert_db.png | Bin 0 -> 744 bytes SQLiteStudio3/guiSQLiteStudio/img/database.png | Bin 0 -> 569 bytes .../guiSQLiteStudio/img/database_connect.png | Bin 0 -> 1370 bytes .../guiSQLiteStudio/img/database_connected.png | Bin 0 -> 725 bytes .../guiSQLiteStudio/img/database_disconnect.png | Bin 0 -> 1520 bytes .../guiSQLiteStudio/img/database_export.png | Bin 0 -> 941 bytes .../guiSQLiteStudio/img/database_export_wizard.svg | 284 ++ .../guiSQLiteStudio/img/database_file.png | Bin 0 -> 701 bytes .../guiSQLiteStudio/img/database_import_wizard.svg | 284 ++ .../guiSQLiteStudio/img/database_invalid.png | Bin 0 -> 692 bytes .../guiSQLiteStudio/img/database_network.png | Bin 0 -> 619 bytes .../guiSQLiteStudio/img/database_offline.png | Bin 0 -> 495 bytes .../guiSQLiteStudio/img/database_online.png | Bin 0 -> 569 bytes .../guiSQLiteStudio/img/database_reload.png | Bin 0 -> 697 bytes SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png | Bin 0 -> 845 bytes SQLiteStudio3/guiSQLiteStudio/img/default.png | Bin 0 -> 591 bytes SQLiteStudio3/guiSQLiteStudio/img/delete_row.png | Bin 0 -> 479 bytes .../guiSQLiteStudio/img/delete_selected.png | Bin 0 -> 512 bytes SQLiteStudio3/guiSQLiteStudio/img/delete_small.png | Bin 0 -> 348 bytes SQLiteStudio3/guiSQLiteStudio/img/denied_small.png | Bin 0 -> 338 bytes SQLiteStudio3/guiSQLiteStudio/img/directory.png | Bin 0 -> 401 bytes .../guiSQLiteStudio/img/directory_open.png | Bin 0 -> 511 bytes .../guiSQLiteStudio/img/directory_open_with_db.png | Bin 0 -> 746 bytes .../guiSQLiteStudio/img/directory_with_db.png | Bin 0 -> 663 bytes SQLiteStudio3/guiSQLiteStudio/img/edit_small.png | Bin 0 -> 314 bytes SQLiteStudio3/guiSQLiteStudio/img/erase.png | Bin 0 -> 680 bytes SQLiteStudio3/guiSQLiteStudio/img/error_small.png | Bin 0 -> 337 bytes SQLiteStudio3/guiSQLiteStudio/img/exec_query.png | Bin 0 -> 470 bytes .../guiSQLiteStudio/img/explain_query.png | Bin 0 -> 524 bytes SQLiteStudio3/guiSQLiteStudio/img/export.png | Bin 0 -> 813 bytes .../guiSQLiteStudio/img/export_file_browse.png | Bin 0 -> 507 bytes .../guiSQLiteStudio/img/feature_request.png | Bin 0 -> 818 bytes SQLiteStudio3/guiSQLiteStudio/img/fk.png | Bin 0 -> 673 bytes SQLiteStudio3/guiSQLiteStudio/img/font_browse.png | Bin 0 -> 340 bytes SQLiteStudio3/guiSQLiteStudio/img/format_sql.png | Bin 0 -> 445 bytes SQLiteStudio3/guiSQLiteStudio/img/function.png | Bin 0 -> 372 bytes SQLiteStudio3/guiSQLiteStudio/img/get_update.png | Bin 0 -> 1153 bytes SQLiteStudio3/guiSQLiteStudio/img/go_back.png | Bin 0 -> 631 bytes SQLiteStudio3/guiSQLiteStudio/img/help.png | Bin 0 -> 621 bytes SQLiteStudio3/guiSQLiteStudio/img/homepage.png | Bin 0 -> 961 bytes SQLiteStudio3/guiSQLiteStudio/img/import.png | Bin 0 -> 771 bytes SQLiteStudio3/guiSQLiteStudio/img/index.png | Bin 0 -> 749 bytes SQLiteStudio3/guiSQLiteStudio/img/indexes.png | Bin 0 -> 759 bytes .../guiSQLiteStudio/img/indicator_error.png | Bin 0 -> 346 bytes .../guiSQLiteStudio/img/indicator_hint.png | Bin 0 -> 453 bytes .../guiSQLiteStudio/img/indicator_info.png | Bin 0 -> 370 bytes .../guiSQLiteStudio/img/indicator_warn.png | Bin 0 -> 429 bytes SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png | Bin 0 -> 745 bytes SQLiteStudio3/guiSQLiteStudio/img/info_small.png | Bin 0 -> 357 bytes SQLiteStudio3/guiSQLiteStudio/img/insert_row.png | Bin 0 -> 583 bytes SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png | Bin 0 -> 565 bytes .../guiSQLiteStudio/img/integrity_check.png | Bin 0 -> 845 bytes SQLiteStudio3/guiSQLiteStudio/img/keyboard.png | Bin 0 -> 1485 bytes SQLiteStudio3/guiSQLiteStudio/img/keyword.png | Bin 0 -> 603 bytes SQLiteStudio3/guiSQLiteStudio/img/licenses.png | Bin 0 -> 707 bytes SQLiteStudio3/guiSQLiteStudio/img/loading.gif | Bin 0 -> 1849 bytes SQLiteStudio3/guiSQLiteStudio/img/minus_small.png | Bin 0 -> 351 bytes SQLiteStudio3/guiSQLiteStudio/img/move_down.png | Bin 0 -> 659 bytes SQLiteStudio3/guiSQLiteStudio/img/move_up.png | Bin 0 -> 651 bytes SQLiteStudio3/guiSQLiteStudio/img/not_null.png | Bin 0 -> 753 bytes SQLiteStudio3/guiSQLiteStudio/img/open_forum.png | Bin 0 -> 1510 bytes .../guiSQLiteStudio/img/open_sql_editor.png | Bin 0 -> 648 bytes .../guiSQLiteStudio/img/open_sql_file.png | Bin 0 -> 511 bytes .../guiSQLiteStudio/img/open_value_editor.png | Bin 0 -> 514 bytes SQLiteStudio3/guiSQLiteStudio/img/page_first.png | Bin 0 -> 684 bytes SQLiteStudio3/guiSQLiteStudio/img/page_last.png | Bin 0 -> 659 bytes SQLiteStudio3/guiSQLiteStudio/img/page_next.png | Bin 0 -> 658 bytes SQLiteStudio3/guiSQLiteStudio/img/page_prev.png | Bin 0 -> 658 bytes SQLiteStudio3/guiSQLiteStudio/img/pk.png | Bin 0 -> 689 bytes SQLiteStudio3/guiSQLiteStudio/img/plugin.png | Bin 0 -> 768 bytes SQLiteStudio3/guiSQLiteStudio/img/plus_small.png | Bin 0 -> 358 bytes .../guiSQLiteStudio/img/question_small.png | Bin 0 -> 365 bytes SQLiteStudio3/guiSQLiteStudio/img/reload.png | Bin 0 -> 659 bytes .../guiSQLiteStudio/img/rename_fn_arg.png | Bin 0 -> 639 bytes .../guiSQLiteStudio/img/results_below.png | Bin 0 -> 361 bytes .../guiSQLiteStudio/img/results_in_tab.png | Bin 0 -> 343 bytes SQLiteStudio3/guiSQLiteStudio/img/rollback.png | Bin 0 -> 588 bytes .../guiSQLiteStudio/img/save_sql_file.png | Bin 0 -> 507 bytes SQLiteStudio3/guiSQLiteStudio/img/set_null.png | Bin 0 -> 667 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png | Bin 0 -> 429 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png | Bin 0 -> 443 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png | Bin 0 -> 446 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png | Bin 0 -> 446 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png | Bin 0 -> 443 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png | Bin 0 -> 444 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png | Bin 0 -> 432 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png | Bin 0 -> 442 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png | Bin 0 -> 443 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png | Bin 0 -> 456 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png | Bin 0 -> 453 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png | Bin 0 -> 474 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png | Bin 0 -> 473 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png | Bin 0 -> 467 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png | Bin 0 -> 463 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png | Bin 0 -> 468 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png | Bin 0 -> 465 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png | Bin 0 -> 472 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png | Bin 0 -> 466 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png | Bin 0 -> 472 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png | Bin 0 -> 497 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png | Bin 0 -> 1489 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png | Bin 0 -> 376 bytes .../guiSQLiteStudio/img/sort_ind_desc.png | Bin 0 -> 383 bytes SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png | Bin 0 -> 681 bytes SQLiteStudio3/guiSQLiteStudio/img/sql.png | Bin 0 -> 376 bytes SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png | Bin 0 -> 670 bytes .../guiSQLiteStudio/img/sqlitestudio.icns | Bin 0 -> 287952 bytes SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico | Bin 0 -> 297966 bytes SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg | 384 ++ .../guiSQLiteStudio/img/sqlitestudio_16.png | Bin 0 -> 1381 bytes SQLiteStudio3/guiSQLiteStudio/img/status_error.png | Bin 0 -> 696 bytes SQLiteStudio3/guiSQLiteStudio/img/status_info.png | Bin 0 -> 744 bytes SQLiteStudio3/guiSQLiteStudio/img/status_warn.png | Bin 0 -> 722 bytes SQLiteStudio3/guiSQLiteStudio/img/table.png | Bin 0 -> 563 bytes .../guiSQLiteStudio/img/table_column_add.png | Bin 0 -> 636 bytes .../guiSQLiteStudio/img/table_column_delete.png | Bin 0 -> 660 bytes .../guiSQLiteStudio/img/table_column_edit.png | Bin 0 -> 783 bytes .../guiSQLiteStudio/img/table_constraint.png | Bin 0 -> 802 bytes .../guiSQLiteStudio/img/table_create_similar.png | Bin 0 -> 574 bytes SQLiteStudio3/guiSQLiteStudio/img/table_export.png | Bin 0 -> 930 bytes SQLiteStudio3/guiSQLiteStudio/img/table_import.png | Bin 0 -> 975 bytes .../guiSQLiteStudio/img/table_populate.png | Bin 0 -> 718 bytes SQLiteStudio3/guiSQLiteStudio/img/tables.png | Bin 0 -> 654 bytes .../guiSQLiteStudio/img/tabs_at_bottom.png | Bin 0 -> 364 bytes SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png | Bin 0 -> 352 bytes .../guiSQLiteStudio/img/test_conn_error.png | Bin 0 -> 874 bytes SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png | Bin 0 -> 905 bytes SQLiteStudio3/guiSQLiteStudio/img/tip.png | Bin 0 -> 743 bytes SQLiteStudio3/guiSQLiteStudio/img/trigger.png | Bin 0 -> 736 bytes .../guiSQLiteStudio/img/trigger_columns.png | Bin 0 -> 300 bytes SQLiteStudio3/guiSQLiteStudio/img/triggers.png | Bin 0 -> 680 bytes SQLiteStudio3/guiSQLiteStudio/img/unique.png | Bin 0 -> 630 bytes SQLiteStudio3/guiSQLiteStudio/img/user.png | Bin 0 -> 744 bytes SQLiteStudio3/guiSQLiteStudio/img/user_manual.png | Bin 0 -> 628 bytes SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png | Bin 0 -> 614 bytes SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png | Bin 0 -> 933 bytes SQLiteStudio3/guiSQLiteStudio/img/view.png | Bin 0 -> 813 bytes SQLiteStudio3/guiSQLiteStudio/img/views.png | Bin 0 -> 1283 bytes .../guiSQLiteStudio/img/virtual_table.png | Bin 0 -> 671 bytes SQLiteStudio3/guiSQLiteStudio/img/warn_small.png | Bin 0 -> 424 bytes SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png | Bin 0 -> 452 bytes SQLiteStudio3/guiSQLiteStudio/img/win_tile.png | Bin 0 -> 408 bytes .../guiSQLiteStudio/img/win_tile_horizontal.png | Bin 0 -> 434 bytes .../guiSQLiteStudio/img/win_tile_vertical.png | Bin 0 -> 324 bytes SQLiteStudio3/guiSQLiteStudio/img/window_close.png | Bin 0 -> 386 bytes .../guiSQLiteStudio/img/window_close_all.png | Bin 0 -> 551 bytes .../guiSQLiteStudio/img/window_close_other.png | Bin 0 -> 698 bytes .../guiSQLiteStudio/img/window_rename.png | Bin 0 -> 586 bytes .../guiSQLiteStudio/img/window_restore.png | Bin 0 -> 479 bytes SQLiteStudio3/guiSQLiteStudio/license.txt | 502 ++ SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp | 909 ++++ SQLiteStudio3/guiSQLiteStudio/mainwindow.h | 234 + SQLiteStudio3/guiSQLiteStudio/mainwindow.ui | 142 + SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp | 264 + SQLiteStudio3/guiSQLiteStudio/mdiarea.h | 73 + SQLiteStudio3/guiSQLiteStudio/mdichild.cpp | 77 + SQLiteStudio3/guiSQLiteStudio/mdichild.h | 47 + SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp | 201 + SQLiteStudio3/guiSQLiteStudio/mdiwindow.h | 43 + .../guiSQLiteStudio/multieditor/multieditor.cpp | 366 ++ .../guiSQLiteStudio/multieditor/multieditor.h | 96 + .../multieditor/multieditorbool.cpp | 215 + .../guiSQLiteStudio/multieditor/multieditorbool.h | 68 + .../multieditor/multieditordate.cpp | 87 + .../guiSQLiteStudio/multieditor/multieditordate.h | 37 + .../multieditor/multieditordatetime.cpp | 275 + .../multieditor/multieditordatetime.h | 84 + .../multieditor/multieditordialog.cpp | 49 + .../multieditor/multieditordialog.h | 29 + .../guiSQLiteStudio/multieditor/multieditorhex.cpp | 94 + .../guiSQLiteStudio/multieditor/multieditorhex.h | 50 + .../multieditor/multieditornumeric.cpp | 109 + .../multieditor/multieditornumeric.h | 42 + .../multieditor/multieditortext.cpp | 184 + .../guiSQLiteStudio/multieditor/multieditortext.h | 87 + .../multieditor/multieditortime.cpp | 90 + .../guiSQLiteStudio/multieditor/multieditortime.h | 38 + .../multieditor/multieditorwidget.cpp | 23 + .../multieditor/multieditorwidget.h | 33 + .../multieditor/multieditorwidgetplugin.h | 17 + .../guiSQLiteStudio/qhexedit2/commands.cpp | 115 + SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h | 70 + .../guiSQLiteStudio/qhexedit2/qhexedit.cpp | 180 + SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h | 230 + .../guiSQLiteStudio/qhexedit2/qhexedit_p.cpp | 883 ++++ .../guiSQLiteStudio/qhexedit2/qhexedit_p.h | 129 + .../guiSQLiteStudio/qhexedit2/xbytearray.cpp | 167 + .../guiSQLiteStudio/qhexedit2/xbytearray.h | 67 + .../guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp | 359 ++ .../guiSQLiteStudio/qtscriptsyntaxhighlighter.h | 75 + .../guiSQLiteStudio/searchtextlocator.cpp | 204 + SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h | 75 + .../guiSQLiteStudio/selectabledbmodel.cpp | 109 + SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h | 36 + .../guiSQLiteStudio/selectabledbobjmodel.cpp | 244 + .../guiSQLiteStudio/selectabledbobjmodel.h | 44 + SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp | 143 + SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h | 34 + SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp | 1521 ++++++ SQLiteStudio3/guiSQLiteStudio/sqleditor.h | 280 ++ .../guiSQLiteStudio/sqlitesyntaxhighlighter.cpp | 447 ++ .../guiSQLiteStudio/sqlitesyntaxhighlighter.h | 185 + SQLiteStudio3/guiSQLiteStudio/sqlview.cpp | 43 + SQLiteStudio3/guiSQLiteStudio/sqlview.h | 25 + SQLiteStudio3/guiSQLiteStudio/statusfield.cpp | 218 + SQLiteStudio3/guiSQLiteStudio/statusfield.h | 58 + SQLiteStudio3/guiSQLiteStudio/statusfield.ui | 97 + .../guiSQLiteStudio/syntaxhighlighterplugin.h | 17 + SQLiteStudio3/guiSQLiteStudio/taskbar.cpp | 299 ++ SQLiteStudio3/guiSQLiteStudio/taskbar.h | 73 + SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp | 68 + SQLiteStudio3/guiSQLiteStudio/uiconfig.h | 90 + SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp | 30 + SQLiteStudio3/guiSQLiteStudio/uicustomicon.h | 16 + SQLiteStudio3/guiSQLiteStudio/uidebug.cpp | 116 + SQLiteStudio3/guiSQLiteStudio/uidebug.h | 36 + SQLiteStudio3/guiSQLiteStudio/uiloader.cpp | 95 + SQLiteStudio3/guiSQLiteStudio/uiloader.h | 33 + .../guiSQLiteStudio/uiloaderpropertyhandler.h | 16 + SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp | 26 + SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h | 16 + SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp | 75 + SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h | 37 + SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp | 27 + SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h | 16 + SQLiteStudio3/guiSQLiteStudio/uiutils.cpp | 123 + SQLiteStudio3/guiSQLiteStudio/uiutils.h | 23 + SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp | 137 + SQLiteStudio3/guiSQLiteStudio/widgetresizer.h | 47 + .../windows/bugreporthistorywindow.cpp | 155 + .../windows/bugreporthistorywindow.h | 65 + .../windows/bugreporthistorywindow.ui | 55 + .../guiSQLiteStudio/windows/collationseditor.cpp | 389 ++ .../guiSQLiteStudio/windows/collationseditor.h | 89 + .../guiSQLiteStudio/windows/collationseditor.ui | 210 + .../windows/collationseditormodel.cpp | 287 ++ .../windows/collationseditormodel.h | 77 + .../guiSQLiteStudio/windows/constrainttabmodel.cpp | 395 ++ .../guiSQLiteStudio/windows/constrainttabmodel.h | 70 + .../guiSQLiteStudio/windows/ddlhistorywindow.cpp | 150 + .../guiSQLiteStudio/windows/ddlhistorywindow.h | 54 + .../guiSQLiteStudio/windows/ddlhistorywindow.ui | 126 + .../guiSQLiteStudio/windows/editorwindow.cpp | 652 +++ .../guiSQLiteStudio/windows/editorwindow.h | 155 + .../guiSQLiteStudio/windows/editorwindow.ui | 137 + .../guiSQLiteStudio/windows/functionseditor.cpp | 632 +++ .../guiSQLiteStudio/windows/functionseditor.h | 109 + .../guiSQLiteStudio/windows/functionseditor.ui | 346 ++ .../windows/functionseditormodel.cpp | 348 ++ .../guiSQLiteStudio/windows/functionseditormodel.h | 98 + .../windows/tableconstraintsmodel.cpp | 482 ++ .../windows/tableconstraintsmodel.h | 72 + .../windows/tablestructuremodel.cpp | 604 +++ .../guiSQLiteStudio/windows/tablestructuremodel.h | 89 + .../guiSQLiteStudio/windows/tablewindow.cpp | 1508 ++++++ .../guiSQLiteStudio/windows/tablewindow.h | 241 + .../guiSQLiteStudio/windows/tablewindow.ui | 307 ++ .../guiSQLiteStudio/windows/viewwindow.cpp | 760 +++ SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h | 146 + .../guiSQLiteStudio/windows/viewwindow.ui | 137 + SQLiteStudio3/plugins.pri | 138 + .../sqlitestudio/SQLiteStudio.exe.manifest | 19 + SQLiteStudio3/sqlitestudio/main.cpp | 142 + SQLiteStudio3/sqlitestudio/sqlitestudio.pro | 46 + SQLiteStudio3/sqlitestudio/windows.rc | 4 + SQLiteStudio3/sqlitestudiocli/cli.cpp | 321 ++ SQLiteStudio3/sqlitestudiocli/cli.h | 62 + SQLiteStudio3/sqlitestudiocli/cli_config.cpp | 55 + SQLiteStudio3/sqlitestudiocli/cli_config.h | 38 + .../sqlitestudiocli/clicommandexecutor.cpp | 24 + SQLiteStudio3/sqlitestudiocli/clicommandexecutor.h | 26 + SQLiteStudio3/sqlitestudiocli/clicommandsyntax.cpp | 433 ++ SQLiteStudio3/sqlitestudiocli/clicommandsyntax.h | 97 + SQLiteStudio3/sqlitestudiocli/clicompleter.cpp | 173 + SQLiteStudio3/sqlitestudiocli/clicompleter.h | 30 + SQLiteStudio3/sqlitestudiocli/climsghandler.cpp | 38 + SQLiteStudio3/sqlitestudiocli/climsghandler.h | 9 + SQLiteStudio3/sqlitestudiocli/cliutils.cpp | 99 + SQLiteStudio3/sqlitestudiocli/cliutils.h | 22 + .../sqlitestudiocli/commands/clicommand.cpp | 334 ++ .../sqlitestudiocli/commands/clicommand.h | 97 + .../sqlitestudiocli/commands/clicommandadd.cpp | 36 + .../sqlitestudiocli/commands/clicommandadd.h | 15 + .../sqlitestudiocli/commands/clicommandcd.cpp | 34 + .../sqlitestudiocli/commands/clicommandcd.h | 15 + .../sqlitestudiocli/commands/clicommandclose.cpp | 51 + .../sqlitestudiocli/commands/clicommandclose.h | 15 + .../sqlitestudiocli/commands/clicommanddblist.cpp | 86 + .../sqlitestudiocli/commands/clicommanddblist.h | 15 + .../sqlitestudiocli/commands/clicommanddesc.cpp | 26 + .../sqlitestudiocli/commands/clicommanddesc.h | 16 + .../sqlitestudiocli/commands/clicommanddir.cpp | 50 + .../sqlitestudiocli/commands/clicommanddir.h | 15 + .../sqlitestudiocli/commands/clicommandexit.cpp | 26 + .../sqlitestudiocli/commands/clicommandexit.h | 15 + .../sqlitestudiocli/commands/clicommandfactory.cpp | 85 + .../sqlitestudiocli/commands/clicommandfactory.h | 25 + .../sqlitestudiocli/commands/clicommandhelp.cpp | 86 + .../sqlitestudiocli/commands/clicommandhelp.h | 19 + .../sqlitestudiocli/commands/clicommandhistory.cpp | 81 + .../sqlitestudiocli/commands/clicommandhistory.h | 26 + .../sqlitestudiocli/commands/clicommandmode.cpp | 65 + .../sqlitestudiocli/commands/clicommandmode.h | 21 + .../commands/clicommandnullvalue.cpp | 32 + .../sqlitestudiocli/commands/clicommandnullvalue.h | 15 + .../sqlitestudiocli/commands/clicommandopen.cpp | 84 + .../sqlitestudiocli/commands/clicommandopen.h | 15 + .../sqlitestudiocli/commands/clicommandpwd.cpp | 28 + .../sqlitestudiocli/commands/clicommandpwd.h | 15 + .../sqlitestudiocli/commands/clicommandremove.cpp | 51 + .../sqlitestudiocli/commands/clicommandremove.h | 15 + .../sqlitestudiocli/commands/clicommandsql.cpp | 506 ++ .../sqlitestudiocli/commands/clicommandsql.h | 66 + .../sqlitestudiocli/commands/clicommandtables.cpp | 79 + .../sqlitestudiocli/commands/clicommandtables.h | 21 + .../sqlitestudiocli/commands/clicommandtree.cpp | 154 + .../sqlitestudiocli/commands/clicommandtree.h | 31 + .../sqlitestudiocli/commands/clicommanduse.cpp | 64 + .../sqlitestudiocli/commands/clicommanduse.h | 15 + SQLiteStudio3/sqlitestudiocli/main.cpp | 92 + SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro | 95 + SQLiteStudio3/utils.pri | 36 + 1179 files changed, 156797 insertions(+) create mode 100644 Plugins/ConfigMigration/ConfigMigration.pro create mode 100644 Plugins/ConfigMigration/config_migration.png create mode 100644 Plugins/ConfigMigration/configmigartion.json create mode 100644 Plugins/ConfigMigration/configmigration.cpp create mode 100644 Plugins/ConfigMigration/configmigration.h create mode 100644 Plugins/ConfigMigration/configmigration.json create mode 100644 Plugins/ConfigMigration/configmigration.qrc create mode 100644 Plugins/ConfigMigration/configmigration_global.h create mode 100644 Plugins/ConfigMigration/configmigrationitem.h create mode 100644 Plugins/ConfigMigration/configmigrationwizard.cpp create mode 100644 Plugins/ConfigMigration/configmigrationwizard.h create mode 100644 Plugins/ConfigMigration/configmigrationwizard.ui create mode 100644 Plugins/CsvExport/CsvExport.pro create mode 100644 Plugins/CsvExport/CsvExport.ui create mode 100644 Plugins/CsvExport/csvexport.cpp create mode 100644 Plugins/CsvExport/csvexport.h create mode 100644 Plugins/CsvExport/csvexport.json create mode 100644 Plugins/CsvExport/csvexport.qrc create mode 100644 Plugins/CsvExport/csvexport_global.h create mode 100644 Plugins/CsvImport/CsvImport.pro create mode 100644 Plugins/CsvImport/CsvImportOptions.ui create mode 100644 Plugins/CsvImport/csvimport.cpp create mode 100644 Plugins/CsvImport/csvimport.h create mode 100644 Plugins/CsvImport/csvimport.json create mode 100644 Plugins/CsvImport/csvimport.qrc create mode 100644 Plugins/CsvImport/csvimport_global.h create mode 100644 Plugins/DbSqlite2/DbSqlite2.pro create mode 100644 Plugins/DbSqlite2/dbsqlite2.cpp create mode 100644 Plugins/DbSqlite2/dbsqlite2.h create mode 100644 Plugins/DbSqlite2/dbsqlite2.json create mode 100644 Plugins/DbSqlite2/dbsqlite2_global.h create mode 100644 Plugins/DbSqlite2/dbsqlite2instance.cpp create mode 100644 Plugins/DbSqlite2/dbsqlite2instance.h create mode 100644 Plugins/HtmlExport/HtmlExport.pro create mode 100644 Plugins/HtmlExport/htmlexport.cpp create mode 100644 Plugins/HtmlExport/htmlexport.css create mode 100644 Plugins/HtmlExport/htmlexport.h create mode 100644 Plugins/HtmlExport/htmlexport.json create mode 100644 Plugins/HtmlExport/htmlexport.qrc create mode 100644 Plugins/HtmlExport/htmlexport.ui create mode 100644 Plugins/HtmlExport/htmlexport_global.h create mode 100644 Plugins/JsonExport/JsonExport.pro create mode 100644 Plugins/JsonExport/jsonexport.cpp create mode 100644 Plugins/JsonExport/jsonexport.h create mode 100644 Plugins/JsonExport/jsonexport.json create mode 100644 Plugins/JsonExport/jsonexport.qrc create mode 100644 Plugins/JsonExport/jsonexport.ui create mode 100644 Plugins/JsonExport/jsonexport_global.h create mode 100644 Plugins/PdfExport/PdfExport.pro create mode 100644 Plugins/PdfExport/pdfexport.cpp create mode 100644 Plugins/PdfExport/pdfexport.h create mode 100644 Plugins/PdfExport/pdfexport.json create mode 100644 Plugins/PdfExport/pdfexport.qrc create mode 100644 Plugins/PdfExport/pdfexport.ui create mode 100644 Plugins/PdfExport/pdfexport_global.h create mode 100644 Plugins/Plugins.pro create mode 100644 Plugins/Printing/Printing.pro create mode 100644 Plugins/Printing/printer.png create mode 100644 Plugins/Printing/printing.cpp create mode 100644 Plugins/Printing/printing.h create mode 100644 Plugins/Printing/printing.json create mode 100644 Plugins/Printing/printing.qrc create mode 100644 Plugins/Printing/printing_global.h create mode 100644 Plugins/Printing/printingexport.cpp create mode 100644 Plugins/Printing/printingexport.h create mode 100644 Plugins/RegExpImport/RegExpImport.pro create mode 100644 Plugins/RegExpImport/regexpimport.cpp create mode 100644 Plugins/RegExpImport/regexpimport.h create mode 100644 Plugins/RegExpImport/regexpimport.json create mode 100644 Plugins/RegExpImport/regexpimport.qrc create mode 100644 Plugins/RegExpImport/regexpimport.ui create mode 100644 Plugins/RegExpImport/regexpimport_global.h create mode 100644 Plugins/ScriptingTcl/ScriptingTcl.pro create mode 100644 Plugins/ScriptingTcl/scriptingtcl.cpp create mode 100644 Plugins/ScriptingTcl/scriptingtcl.h create mode 100644 Plugins/ScriptingTcl/scriptingtcl.json create mode 100644 Plugins/ScriptingTcl/scriptingtcl.png create mode 100644 Plugins/ScriptingTcl/scriptingtcl.qrc create mode 100644 Plugins/ScriptingTcl/scriptingtcl_global.h create mode 100644 Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro create mode 100644 Plugins/SqlEnterpriseFormatter/formataltertable.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formataltertable.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatanalyze.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatanalyze.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatattach.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatattach.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatbegintrans.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatbegintrans.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatcolumntype.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatcolumntype.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatcommittrans.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatcommittrans.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatcopy.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatcopy.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreateindex.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreateindex.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreatetable.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreatetable.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreatetrigger.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreatetrigger.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreateview.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreateview.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreatevirtualtable.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatcreatevirtualtable.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatdelete.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatdelete.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatdetach.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatdetach.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatdropindex.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatdropindex.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatdroptable.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatdroptable.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatdroptrigger.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatdroptrigger.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatdropview.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatdropview.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatempty.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatempty.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatexpr.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatexpr.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatforeignkey.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatforeignkey.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatindexedcolumn.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatindexedcolumn.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatinsert.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatinsert.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatlimit.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatlimit.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatorderby.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatorderby.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatpragma.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatpragma.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatraise.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatraise.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatreindex.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatreindex.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatrelease.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatrelease.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatrollback.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatrollback.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatsavepoint.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatsavepoint.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatselect.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatselect.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatstatement.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatstatement.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatupdate.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatupdate.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatvacuum.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatvacuum.h create mode 100644 Plugins/SqlEnterpriseFormatter/formatwith.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/formatwith.h create mode 100644 Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp create mode 100644 Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h create mode 100644 Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.json create mode 100644 Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.qrc create mode 100644 Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui create mode 100644 Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter_global.h create mode 100644 Plugins/SqlExport/SqlExport.pro create mode 100644 Plugins/SqlExport/SqlExportCommon.ui create mode 100644 Plugins/SqlExport/SqlExportQuery.ui create mode 100644 Plugins/SqlExport/sqlexport.cpp create mode 100644 Plugins/SqlExport/sqlexport.h create mode 100644 Plugins/SqlExport/sqlexport.json create mode 100644 Plugins/SqlExport/sqlexport.qrc create mode 100644 Plugins/SqlExport/sqlexport_global.h create mode 100644 Plugins/SqlFormatterSimple/SqlFormatterSimple.pro create mode 100644 Plugins/SqlFormatterSimple/SqlFormatterSimple.ui create mode 100644 Plugins/SqlFormatterSimple/sqlformattersimple.json create mode 100644 Plugins/SqlFormatterSimple/sqlformattersimple.qrc create mode 100644 Plugins/SqlFormatterSimple/sqlformattersimple_global.h create mode 100644 Plugins/SqlFormatterSimple/sqlformattersimpleplugin.cpp create mode 100644 Plugins/SqlFormatterSimple/sqlformattersimpleplugin.h create mode 100644 Plugins/XmlExport/XmlExport.pro create mode 100644 Plugins/XmlExport/XmlExport.ui create mode 100644 Plugins/XmlExport/xmlexport.cpp create mode 100644 Plugins/XmlExport/xmlexport.h create mode 100644 Plugins/XmlExport/xmlexport.json create mode 100644 Plugins/XmlExport/xmlexport.qrc create mode 100644 Plugins/XmlExport/xmlexport_global.h create mode 100644 SQLiteStudio3/SQLiteStudio3.pro create mode 100644 SQLiteStudio3/Tests/CompletionHelperTest/CompletionHelperTest.pro create mode 100644 SQLiteStudio3/Tests/CompletionHelperTest/tst_completionhelpertest.cpp create mode 100644 SQLiteStudio3/Tests/DbVersionConverterTest/DbVersionConverterTest.pro create mode 100644 SQLiteStudio3/Tests/DbVersionConverterTest/tst_dbversionconvertertesttest.cpp create mode 100644 SQLiteStudio3/Tests/DsvFormatsTest/DsvFormatsTest.pro create mode 100644 SQLiteStudio3/Tests/DsvFormatsTest/tst_dsvformatstesttest.cpp create mode 100644 SQLiteStudio3/Tests/HashTablesTest/HashTablesTest.pro create mode 100644 SQLiteStudio3/Tests/HashTablesTest/tst_hashtablestesttest.cpp create mode 100644 SQLiteStudio3/Tests/ParserTest/ParserTest.pro create mode 100644 SQLiteStudio3/Tests/ParserTest/tst_parsertest.cpp create mode 100644 SQLiteStudio3/Tests/SelectResolverTest/SelectResolverTest.pro create mode 100644 SQLiteStudio3/Tests/SelectResolverTest/tst_selectresolvertest.cpp create mode 100644 SQLiteStudio3/Tests/TableModifierTest/TableModifierTest.pro create mode 100644 SQLiteStudio3/Tests/TableModifierTest/tst_tablemodifiertest.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/TestUtils.pro create mode 100644 SQLiteStudio3/Tests/TestUtils/collationmanagermock.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/collationmanagermock.h create mode 100644 SQLiteStudio3/Tests/TestUtils/configmock.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/configmock.h create mode 100644 SQLiteStudio3/Tests/TestUtils/dbattachermock.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/dbattachermock.h create mode 100644 SQLiteStudio3/Tests/TestUtils/dbmanagermock.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/dbmanagermock.h create mode 100644 SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.h create mode 100644 SQLiteStudio3/Tests/TestUtils/functionmanagermock.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/functionmanagermock.h create mode 100644 SQLiteStudio3/Tests/TestUtils/hippomocks.h create mode 100644 SQLiteStudio3/Tests/TestUtils/mocks.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/mocks.h create mode 100644 SQLiteStudio3/Tests/TestUtils/pluginmanagermock.cpp create mode 100644 SQLiteStudio3/Tests/TestUtils/pluginmanagermock.h create mode 100644 SQLiteStudio3/Tests/TestUtils/test_common.pri create mode 100644 SQLiteStudio3/Tests/TestUtils/testutils_global.h create mode 100644 SQLiteStudio3/Tests/Tests.pro create mode 100644 SQLiteStudio3/Tests/testdirs.pri create mode 100644 SQLiteStudio3/UpdateSQLiteStudio/UpdateSQLiteStudio.exe.manifest create mode 100644 SQLiteStudio3/UpdateSQLiteStudio/UpdateSQLiteStudio.pro create mode 100644 SQLiteStudio3/UpdateSQLiteStudio/main.cpp create mode 100644 SQLiteStudio3/UpdateSQLiteStudio/windows.manifest.autosave create mode 100644 SQLiteStudio3/UpdateSQLiteStudio/windows.rc create mode 100644 SQLiteStudio3/coreSQLiteStudio/TODO.txt create mode 100644 SQLiteStudio3/coreSQLiteStudio/committable.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/committable.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/bihash.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/column.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/column.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/global.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/memoryusage.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/memoryusage.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/nulldevice.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/objectpool.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/strhash.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/table.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/table.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/unused.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/utils.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/utils.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/completioncomparer.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/completionhelper.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro create mode 100644 SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio_global.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/coresqlitestudio.qrc create mode 100644 SQLiteStudio3/coreSQLiteStudio/csvformat.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/csvformat.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/csvserializer.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/datatype.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/datatype.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/attachguard.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/db.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/db.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/dbsqlite.h.autosave create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/dbattacher.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/dbversionconverter.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/dialect.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/expectedtoken.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/exportworker.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/exportworker.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/importworker.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/importworker.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/interruptable.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/licenses/diff_match.txt create mode 100644 SQLiteStudio3/coreSQLiteStudio/licenses/fugue_icons.txt create mode 100644 SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt create mode 100644 SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt create mode 100644 SQLiteStudio3/coreSQLiteStudio/licenses/sqlitestudio_license.txt create mode 100644 SQLiteStudio3/coreSQLiteStudio/log.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/log.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitetablerelatedddl.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/keywords.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/lempar.c create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/lexer.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/parser.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/parsererror.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/parsererror.h create mode 100755 SQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.y create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.y create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/token.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/parser/token.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/pluginloader.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/pluginservicebase.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/populateworker.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/populateworker.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/qio.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/qio.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/querymodel.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/querymodel.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/returncode.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/returncode.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/Key.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/schemaresolver.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/selectresolver.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/codeformatter.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/codeformatter.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/collationmanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/config.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/config.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/dbmanager.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/exportmanager.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/exportmanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/functionmanager.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/importmanager.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/importmanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/notifymanager.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/notifymanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/tablemodifier.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/tsvserializer.h create mode 100644 SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp create mode 100644 SQLiteStudio3/coreSQLiteStudio/viewmodifier.h create mode 100644 SQLiteStudio3/create_linux_portable.sh create mode 100755 SQLiteStudio3/create_macosx_bundle.sh create mode 100755 SQLiteStudio3/create_source_dist.sh create mode 100644 SQLiteStudio3/create_win32_portable.bat create mode 100644 SQLiteStudio3/dirs.pri create mode 100644 SQLiteStudio3/docs/sqlitestudio3_docs.cfg create mode 100644 SQLiteStudio3/docs/sqlitestudio_logo.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/actionentry.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/actionentry.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/colorbutton.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/colorbutton.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/configcombobox.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/configcombobox.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/configradiobutton.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/datawidgetmapper.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extaction.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extaction.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactioncontainer.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactionmanagementnotifier.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extactionprototype.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extlineedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/extlineedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fileedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fileedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fontedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fontedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/fontedit.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/intvalidator.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/intvalidator.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/numericspinbox.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/tablewidget.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/tablewidget.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/userinputfilter.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/verifiablewizardpage.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completeritemdelegate.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completermodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completermodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/configmapper.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/configmapper.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/configuiplugin.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/combodatawidget.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/listtostringlisthash.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/configwidgets/styleconfigwidget.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncheckpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnforeignkeypanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnnotnullpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/columnuniquepanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintcheckpanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablecheckpanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tablepkanduniquepanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableprimarykeypanel.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/constraints/tableuniquepanel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/customconfigwidgetplugin.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dataview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dataview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dblistmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/debugconsole.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/debugconsole.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/formmanager.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/formmanager.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/formview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/formview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro create mode 100644 SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/icon.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/icon.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/iconmanager.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/icons.qrc create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/abort24.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_abort.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_clear.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_copy.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_cut.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_delete.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_paste.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_redo.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_search.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/act_undo.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/bug.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/bug_list.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/check.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/clear_history.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/close.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/collation.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/column.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/columns.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/commit.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/complete.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_number.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_other.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/completer_string.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_colors.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_font.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_general.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/config_style.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/configure.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/convert_db.png create mode 100755 SQLiteStudio3/guiSQLiteStudio/img/database.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_connect.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_connected.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_export.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_file.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_network.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_offline.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_online.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/database_reload.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/default.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/delete_row.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/delete_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/denied_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/directory.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/directory_open.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/edit_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/erase.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/error_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/exec_query.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/explain_query.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/export.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/feature_request.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/fk.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/font_browse.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/format_sql.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/function.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/get_update.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/go_back.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/help.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/homepage.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/import.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/index.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indexes.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/info_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/insert_row.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/keyboard.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/keyword.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/licenses.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/loading.gif create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/minus_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/move_down.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/move_up.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/not_null.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/open_forum.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/page_first.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/page_last.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/page_next.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/page_prev.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/pk.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/plugin.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/plus_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/question_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/reload.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/results_below.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/rollback.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/set_null.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sql.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/status_error.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/status_info.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/status_warn.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_export.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_import.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/table_populate.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/tables.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/tip.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/trigger.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/triggers.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/unique.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/user.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/user_manual.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/view.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/views.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/warn_small.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/win_tile.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_close.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_rename.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/img/window_restore.png create mode 100644 SQLiteStudio3/guiSQLiteStudio/license.txt create mode 100644 SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/mainwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/mainwindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdiarea.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdichild.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdichild.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/mdiwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqleditor.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlview.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/sqlview.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/statusfield.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/statusfield.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/statusfield.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/taskbar.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/taskbar.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiconfig.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uicustomicon.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uidebug.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uidebug.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiloader.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiloader.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiutils.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/uiutils.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/widgetresizer.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h create mode 100644 SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui create mode 100644 SQLiteStudio3/plugins.pri create mode 100644 SQLiteStudio3/sqlitestudio/SQLiteStudio.exe.manifest create mode 100644 SQLiteStudio3/sqlitestudio/main.cpp create mode 100644 SQLiteStudio3/sqlitestudio/sqlitestudio.pro create mode 100644 SQLiteStudio3/sqlitestudio/windows.rc create mode 100644 SQLiteStudio3/sqlitestudiocli/cli.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/cli.h create mode 100644 SQLiteStudio3/sqlitestudiocli/cli_config.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/cli_config.h create mode 100644 SQLiteStudio3/sqlitestudiocli/clicommandexecutor.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/clicommandexecutor.h create mode 100644 SQLiteStudio3/sqlitestudiocli/clicommandsyntax.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/clicommandsyntax.h create mode 100644 SQLiteStudio3/sqlitestudiocli/clicompleter.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/clicompleter.h create mode 100644 SQLiteStudio3/sqlitestudiocli/climsghandler.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/climsghandler.h create mode 100644 SQLiteStudio3/sqlitestudiocli/cliutils.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/cliutils.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommand.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h create mode 100644 SQLiteStudio3/sqlitestudiocli/main.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro create mode 100644 SQLiteStudio3/utils.pri diff --git a/Plugins/ConfigMigration/ConfigMigration.pro b/Plugins/ConfigMigration/ConfigMigration.pro new file mode 100644 index 0000000..965cfeb --- /dev/null +++ b/Plugins/ConfigMigration/ConfigMigration.pro @@ -0,0 +1,31 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-09-15T15:02:05 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT += widgets + +TARGET = ConfigMigration +TEMPLATE = lib + +DEFINES += CONFIGMIGRATION_LIBRARY + +SOURCES += configmigration.cpp \ + configmigrationwizard.cpp + +HEADERS += configmigration.h\ + configmigration_global.h \ + configmigrationwizard.h \ + configmigrationitem.h + +OTHER_FILES += \ + configmigration.json + +FORMS += \ + configmigrationwizard.ui + +RESOURCES += \ + configmigration.qrc diff --git a/Plugins/ConfigMigration/config_migration.png b/Plugins/ConfigMigration/config_migration.png new file mode 100644 index 0000000..eb75995 Binary files /dev/null and b/Plugins/ConfigMigration/config_migration.png differ diff --git a/Plugins/ConfigMigration/configmigartion.json b/Plugins/ConfigMigration/configmigartion.json new file mode 100644 index 0000000..e69de29 diff --git a/Plugins/ConfigMigration/configmigration.cpp b/Plugins/ConfigMigration/configmigration.cpp new file mode 100644 index 0000000..cd49d24 --- /dev/null +++ b/Plugins/ConfigMigration/configmigration.cpp @@ -0,0 +1,188 @@ +#include "configmigration.h" +#include "services/notifymanager.h" +#include "sqlitestudio.h" +#include "mainwindow.h" +#include "statusfield.h" +#include "configmigrationwizard.h" +#include "db/dbsqlite3.h" +#include +#include +#include +#include + +ConfigMigration::ConfigMigration() +{ +} + +bool ConfigMigration::init() +{ + Q_INIT_RESOURCE(configmigration); + + if (cfg.CfgMigration.Migrated.get()) + { + qDebug() << "ConfigMigration: already migrated. Skipping."; + return true; + } + + QString oldCfg = findOldConfig(); + if (!oldCfg.isNull()) + { + db = new DbSqlite3("Old SQLiteStudio settings", oldCfg, {{DB_PURE_INIT, true}}); + if (db->open()) + { + itemsToMigrate = findItemsToMigrate(); + notifyInfo(tr("A configuration from old SQLiteStudio 2.x.x has been detected. " + "Would you like to migrate old settings into the current version? " + "Click here to do that.").arg(ACTION_LINK)); + + connect(MAINWINDOW->getStatusField(), SIGNAL(linkActivated(QString)), this, SLOT(linkActivated(QString))); + db->close(); + } + } + + return true; +} + +void ConfigMigration::deinit() +{ + Q_CLEANUP_RESOURCE(configmigration); + safe_delete(db); + + for (ConfigMigrationItem* item : itemsToMigrate) + delete item; + + itemsToMigrate.clear(); + GenericPlugin::deinit(); +} + +QString ConfigMigration::findOldConfig() +{ + QString output; + QString dirPath; + + // Portable path 1 check + dirPath = QDir::currentPath() + "/sqlitestudio-cfg"; + if (checkOldDir(dirPath, output)) + return output; + + // Portable path 2 check + dirPath = QCoreApplication::applicationDirPath() + "/sqlitestudio-cfg"; + if (checkOldDir(dirPath, output)) + return output; + + // Portable path 3 check + dirPath = QCoreApplication::applicationDirPath() + "/../sqlitestudio-cfg"; + if (checkOldDir(dirPath, output)) + return output; + + if (getDistributionType() == DistributionType::OSX_BOUNDLE) + { + // Portable path 4 check + dirPath = QCoreApplication::applicationDirPath() + "/../../sqlitestudio-cfg"; + if (checkOldDir(dirPath, output)) + return output; + + // Portable path 5 check + dirPath = QCoreApplication::applicationDirPath() + "/../../../sqlitestudio-cfg"; + if (checkOldDir(dirPath, output)) + return output; + } + + // Global path check +#ifdef Q_OS_WIN + if (QSysInfo::windowsVersion() & QSysInfo::WV_NT_based) + dirPath = SQLITESTUDIO->getEnv("APPDATA")+"/sqlitestudio"; + else + dirPath = SQLITESTUDIO->getEnv("HOME")+"/sqlitestudio"; +#else + dirPath = SQLITESTUDIO->getEnv("HOME")+"/.sqlitestudio"; +#endif + + if (checkOldDir(dirPath, output)) + return output; + + return QString(); +} + +bool ConfigMigration::checkOldDir(const QString &dir, QString &output) +{ + QFileInfo fi(dir + "/settings"); + if (fi.exists() && fi.isReadable()) + { + output = fi.absoluteFilePath(); + return true; + } + + return false; +} + +QList ConfigMigration::findItemsToMigrate() +{ + static_qstring(bugsHistoryQuery, "SELECT count(*) FROM bugs"); + static_qstring(dbListQuery, "SELECT count(*) FROM dblist"); + static_qstring(funcListQuery, "SELECT count(*) FROM functions"); + static_qstring(sqlHistoryQuery, "SELECT count(*) FROM history"); + + ConfigMigrationItem* item = nullptr; + QList results; + + int bugReports = db->exec(bugsHistoryQuery)->getSingleCell().toInt(); + if (bugReports > 0) + { + item = new ConfigMigrationItem; + item->type = ConfigMigrationItem::Type::BUG_REPORTS; + item->label = tr("Bug reports history (%1)").arg(bugReports); + results << item; + } + + int dbCount = db->exec(dbListQuery)->getSingleCell().toInt(); + if (dbCount > 0) + { + item = new ConfigMigrationItem; + item->type = ConfigMigrationItem::Type::DATABASES; + item->label = tr("Database list (%1)").arg(dbCount); + results << item; + } + + int funcCount = db->exec(funcListQuery)->getSingleCell().toInt(); + if (funcCount > 0) + { + item = new ConfigMigrationItem; + item->type = ConfigMigrationItem::Type::FUNCTION_LIST; + item->label = tr("Custom SQL functions (%1)").arg(funcCount); + results << item; + } + + int sqlHistory = db->exec(sqlHistoryQuery)->getSingleCell().toInt(); + if (sqlHistory > 0) + { + item = new ConfigMigrationItem; + item->type = ConfigMigrationItem::Type::SQL_HISTORY; + item->label = tr("SQL queries history (%1)").arg(sqlHistory); + results << item; + } + + return results; +} + +Db* ConfigMigration::getOldCfgDb() const +{ + return db; +} + +QList ConfigMigration::getItemsToMigrate() const +{ + return itemsToMigrate; +} + +void ConfigMigration::linkActivated(const QString &link) +{ + if (link != ACTION_LINK) + return; + + ConfigMigrationWizard wizard(MAINWINDOW, this); + wizard.exec(); + + if (wizard.didMigrate()) + cfg.CfgMigration.Migrated.set(true); +} diff --git a/Plugins/ConfigMigration/configmigration.h b/Plugins/ConfigMigration/configmigration.h new file mode 100644 index 0000000..828de3d --- /dev/null +++ b/Plugins/ConfigMigration/configmigration.h @@ -0,0 +1,49 @@ +#ifndef CONFIGMIGRATION_H +#define CONFIGMIGRATION_H + +#include "configmigration_global.h" +#include "plugins/generalpurposeplugin.h" +#include "plugins/genericplugin.h" +#include "configmigrationitem.h" +#include "config_builder.h" +#include + +class Db; + +CFG_CATEGORIES(ConfigMigration, + CFG_CATEGORY(CfgMigration, + CFG_ENTRY(bool, Migrated, false); + ) +) + +class CONFIGMIGRATIONSHARED_EXPORT ConfigMigration : public GenericPlugin, public GeneralPurposePlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("configmigration.json") + + public: + ConfigMigration(); + + bool init(); + void deinit(); + + QList getItemsToMigrate() const; + + Db* getOldCfgDb() const; + + private: + QString findOldConfig(); + bool checkOldDir(const QString& dir, QString& output); + QList findItemsToMigrate(); + + static const constexpr char* ACTION_LINK = "migrateOldConfig"; + + Db* db = nullptr; + QList itemsToMigrate; + CFG_LOCAL_PERSISTABLE(ConfigMigration, cfg) + + private slots: + void linkActivated(const QString& link); +}; + +#endif // CONFIGMIGRATION_H diff --git a/Plugins/ConfigMigration/configmigration.json b/Plugins/ConfigMigration/configmigration.json new file mode 100644 index 0000000..14e5d4b --- /dev/null +++ b/Plugins/ConfigMigration/configmigration.json @@ -0,0 +1,8 @@ +{ + "type": "GeneralPurposePlugin", + "title": "Configuration migration", + "description": "Performs migration from SQLiteStudio 2.1.x configuration to version 3.0.0.", + "version": 10001, + "author": "SalSoft", + "gui": true +} diff --git a/Plugins/ConfigMigration/configmigration.qrc b/Plugins/ConfigMigration/configmigration.qrc new file mode 100644 index 0000000..9df7d5e --- /dev/null +++ b/Plugins/ConfigMigration/configmigration.qrc @@ -0,0 +1,5 @@ + + + config_migration.png + + diff --git a/Plugins/ConfigMigration/configmigration_global.h b/Plugins/ConfigMigration/configmigration_global.h new file mode 100644 index 0000000..7c43a97 --- /dev/null +++ b/Plugins/ConfigMigration/configmigration_global.h @@ -0,0 +1,12 @@ +#ifndef CONFIGMIGRATION_GLOBAL_H +#define CONFIGMIGRATION_GLOBAL_H + +#include + +#if defined(CONFIGMIGRATION_LIBRARY) +# define CONFIGMIGRATIONSHARED_EXPORT Q_DECL_EXPORT +#else +# define CONFIGMIGRATIONSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // CONFIGMIGRATION_GLOBAL_H diff --git a/Plugins/ConfigMigration/configmigrationitem.h b/Plugins/ConfigMigration/configmigrationitem.h new file mode 100644 index 0000000..ef08065 --- /dev/null +++ b/Plugins/ConfigMigration/configmigrationitem.h @@ -0,0 +1,20 @@ +#ifndef CONFIGMIGRATIONITEM_H +#define CONFIGMIGRATIONITEM_H + +#include + +struct ConfigMigrationItem +{ + enum class Type + { + SQL_HISTORY, + DATABASES, + FUNCTION_LIST, + BUG_REPORTS + }; + + QString label; + Type type; +}; + +#endif // CONFIGMIGRATIONITEM_H diff --git a/Plugins/ConfigMigration/configmigrationwizard.cpp b/Plugins/ConfigMigration/configmigrationwizard.cpp new file mode 100644 index 0000000..afdf705 --- /dev/null +++ b/Plugins/ConfigMigration/configmigrationwizard.cpp @@ -0,0 +1,407 @@ +#include "configmigrationwizard.h" +#include "ui_configmigrationwizard.h" +#include "configmigration.h" +#include "configmigrationitem.h" +#include "iconmanager.h" +#include "uiutils.h" +#include "dbtree/dbtree.h" +#include "dbtree/dbtreemodel.h" +#include "services/config.h" +#include "sqlitestudio.h" +#include "db/dbsqlite3.h" +#include "services/notifymanager.h" +#include "services/dbmanager.h" + +ConfigMigrationWizard::ConfigMigrationWizard(QWidget *parent, ConfigMigration* cfgMigration) : + QWizard(parent), + ui(new Ui::ConfigMigrationWizard), + cfgMigration(cfgMigration) +{ + init(); +} + +ConfigMigrationWizard::~ConfigMigrationWizard() +{ + clearFunctions(); + delete ui; +} + +bool ConfigMigrationWizard::didMigrate() +{ + return migrated; +} + +void ConfigMigrationWizard::accept() +{ + migrate(); + QWizard::accept(); +} + +void ConfigMigrationWizard::init() +{ + ui->setupUi(this); + +#ifdef Q_OS_MACX + resize(width() + 150, height()); + setPixmap(QWizard::BackgroundPixmap, addOpacity(ICONMANAGER->getIcon("config_migration")->pixmap(180, 180), 0.4)); +#endif + + ui->optionsPage->setValidator([=]() -> bool + { + QString grpName = ui->groupNameEdit->text(); + + bool grpOk = true; + QString grpErrorMsg; + if (ui->dbGroup->isEnabled() && ui->dbGroup->isChecked()) + { + if (grpName.isEmpty()) + { + grpOk = false; + grpErrorMsg = tr("Enter a non-empty name."); + } + else + { + DbTreeItem* item = DBTREE->getModel()->findItem(DbTreeItem::Type::DIR, grpName); + if (item && !item->parentDbTreeItem()) + { + grpOk = false; + grpErrorMsg = tr("Top level group named '%1' already exists. Enter a group name that does not exist yet.").arg(grpName); + } + } + } + + setValidState(ui->groupNameEdit, grpOk, grpErrorMsg); + + return grpOk; + }); + + + QTreeWidgetItem* treeItem = nullptr; + for (ConfigMigrationItem* cfgItem : cfgMigration->getItemsToMigrate()) + { + treeItem = new QTreeWidgetItem({cfgItem->label}); + treeItem->setData(0, Qt::UserRole, static_cast(cfgItem->type)); + treeItem->setFlags(treeItem->flags() | Qt::ItemIsUserCheckable); + treeItem->setCheckState(0, Qt::Checked); + ui->itemsTree->addTopLevelItem(treeItem); + } + + connect(ui->dbGroup, SIGNAL(clicked()), ui->optionsPage, SIGNAL(completeChanged())); + connect(ui->groupNameEdit, SIGNAL(textChanged(QString)), ui->optionsPage, SIGNAL(completeChanged())); + connect(this, SIGNAL(updateOptionsValidation()), ui->optionsPage, SIGNAL(completeChanged())); + connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(updateOptions())); + + emit updateOptionsValidation(); +} + +void ConfigMigrationWizard::migrate() +{ + Db* oldCfgDb = cfgMigration->getOldCfgDb(); + if (!oldCfgDb->open()) + { + notifyError(tr("Could not open old configuration file in order to migrate settings from it.")); + return; + } + + QString cfgFilePath = SQLITESTUDIO->getConfig()->getConfigFilePath(); + Db* newCfgDb = new DbSqlite3("Config migration connection", cfgFilePath, {{DB_PURE_INIT, true}}); + if (!newCfgDb->open()) + { + notifyError(tr("Could not open current configuration file in order to migrate settings from old configuration file.")); + delete newCfgDb; + return; + } + + newCfgDb->begin(); + bool migrated = migrateSelected(oldCfgDb, newCfgDb); + if (migrated && !newCfgDb->commit()) + { + notifyError(tr("Could not commit migrated data into new configuration file: %1").arg(newCfgDb->getErrorText())); + newCfgDb->rollback(); + } + else if (!migrated) + { + newCfgDb->rollback(); + } + else + { + finalize(); + } + oldCfgDb->close(); + newCfgDb->close(); + delete newCfgDb; + clearFunctions(); +} + +bool ConfigMigrationWizard::migrateSelected(Db* oldCfgDb, Db* newCfgDb) +{ + if (checkedTypes.contains(ConfigMigrationItem::Type::BUG_REPORTS) && !migrateBugReports(oldCfgDb, newCfgDb)) + return false; + + if (checkedTypes.contains(ConfigMigrationItem::Type::DATABASES) && !migrateDatabases(oldCfgDb, newCfgDb)) + return false; + + if (checkedTypes.contains(ConfigMigrationItem::Type::FUNCTION_LIST) && !migrateFunction(oldCfgDb, newCfgDb)) + return false; + + if (checkedTypes.contains(ConfigMigrationItem::Type::SQL_HISTORY) && !migrateSqlHistory(oldCfgDb, newCfgDb)) + return false; + + return true; +} + +bool ConfigMigrationWizard::migrateBugReports(Db* oldCfgDb, Db* newCfgDb) +{ + static_qstring(oldBugsQuery, "SELECT created_on, brief, url, type FROM bugs"); + static_qstring(newBugsInsert, "INSERT INTO reports_history (timestamp, feature_request, title, url) VALUES (?, ?, ?, ?)"); + + SqlQueryPtr insertResults; + SqlResultsRowPtr row; + SqlQueryPtr results = oldCfgDb->exec(oldBugsQuery); + if (results->isError()) + { + notifyError(tr("Could not read bug reports history from old configuration file in order to migrate it: %1").arg(results->getErrorText())); + return false; + } + + bool feature; + QString url; + while (results->hasNext()) + { + row = results->next(); + feature = (row->value("type").toString().toUpper() == "FEATURE"); + url = row->value("url").toString().trimmed(); + if (url.startsWith("http://") && url.contains("sqlitestudio.one.pl")) + url.replace("sqlitestudio.one.pl", "sqlitestudio.pl").replace("report_bug.rvt", "report_bug3.rvt"); + + insertResults = newCfgDb->exec(newBugsInsert, {row->value("created_on"), feature, row->value("brief"), url}); + if (insertResults->isError()) + { + notifyError(tr("Could not insert a bug reports history entry into new configuration file: %1").arg(insertResults->getErrorText())); + return false; + } + } + + return true; +} + +bool ConfigMigrationWizard::migrateDatabases(Db* oldCfgDb, Db* newCfgDb) +{ + static_qstring(oldDbListQuery, "SELECT name, path FROM dblist"); + static_qstring(newDbListInsert, "INSERT INTO dblist (name, path) VALUES (?, ?)"); + static_qstring(groupOrderQuery, "SELECT max([order]) + 1 FROM groups WHERE parent %1"); + static_qstring(groupInsert, "INSERT INTO groups (name, [order], parent, open, dbname) VALUES (?, ?, ?, ?, ?)"); + + SqlQueryPtr groupResults; + SqlQueryPtr insertResults; + SqlResultsRowPtr row; + SqlQueryPtr results = oldCfgDb->exec(oldDbListQuery); + if (results->isError()) + { + notifyError(tr("Could not read database list from old configuration file in order to migrate it: %1").arg(results->getErrorText())); + return false; + } + + // Creating containing group + bool putInGroup = ui->dbGroup->isEnabled() && ui->dbGroup->isChecked(); + qint64 groupId = -1; + int order; + if (putInGroup) + { + // Query order + groupResults = newCfgDb->exec(groupOrderQuery.arg("IS NULL")); + if (groupResults->isError()) + { + notifyError(tr("Could query for available order for containing group in new configuration file in order to migrate the database list: %1") + .arg(groupResults->getErrorText())); + return false; + } + + order = groupResults->getSingleCell().toInt(); + + // Insert group + groupResults = newCfgDb->exec(groupInsert, {ui->groupNameEdit->text(), order, QVariant(), 1, QVariant()}); + if (groupResults->isError()) + { + notifyError(tr("Could not create containing group in new configuration file in order to migrate the database list: %1").arg(groupResults->getErrorText())); + return false; + } + groupId = groupResults->getRegularInsertRowId(); + } + + // Migrating the list + QString name; + QString path; + while (results->hasNext()) + { + row = results->next(); + name = row->value("name").toString(); + path = row->value("path").toString(); + + if (DBLIST->getByName(name) || DBLIST->getByPath(path)) // already on the new list + continue; + + insertResults = newCfgDb->exec(newDbListInsert, {name, path}); + if (insertResults->isError()) + { + notifyError(tr("Could not insert a database entry into new configuration file: %1").arg(insertResults->getErrorText())); + return false; + } + + // Query order + if (putInGroup) + groupResults = newCfgDb->exec(groupOrderQuery.arg("= ?"), {groupId}); + else + groupResults = newCfgDb->exec(groupOrderQuery.arg("IS NULL")); + + if (groupResults->isError()) + { + notifyError(tr("Could query for available order for next database in new configuration file in order to migrate the database list: %1") + .arg(groupResults->getErrorText())); + return false; + } + + order = groupResults->getSingleCell().toInt(); + + // Insert group + groupResults = newCfgDb->exec(groupInsert, {QVariant(), order, putInGroup ? QVariant(groupId) : QVariant(), 0, name}); + if (groupResults->isError()) + { + notifyError(tr("Could not create group referencing the database in new configuration file: %1").arg(groupResults->getErrorText())); + return false; + } + } + + return true; +} + +bool ConfigMigrationWizard::migrateFunction(Db* oldCfgDb, Db* newCfgDb) +{ + UNUSED(newCfgDb); + + static_qstring(oldFunctionsQuery, "SELECT name, type, code FROM functions"); + + SqlResultsRowPtr row; + SqlQueryPtr results = oldCfgDb->exec(oldFunctionsQuery); + if (results->isError()) + { + notifyError(tr("Could not read function list from old configuration file in order to migrate it: %1").arg(results->getErrorText())); + return false; + } + + clearFunctions(); + for (FunctionManager::ScriptFunction* fn : FUNCTIONS->getAllScriptFunctions()) + fnList << new FunctionManager::ScriptFunction(*fn); + + FunctionManager::ScriptFunction* fn = nullptr; + while (results->hasNext()) + { + row = results->next(); + + fn = new FunctionManager::ScriptFunction(); + fn->type = FunctionManager::ScriptFunction::SCALAR; + fn->lang = row->value("type").toString(); + fn->name = row->value("name").toString(); + fn->code = row->value("code").toString(); + fnList << fn; + } + + return true; +} + +bool ConfigMigrationWizard::migrateSqlHistory(Db* oldCfgDb, Db* newCfgDb) +{ + static_qstring(historyIdQuery, "SELECT CASE WHEN max(id) IS NULL THEN 0 ELSE max(id) + 1 END FROM sqleditor_history"); + static_qstring(oldHistoryQuery, "SELECT dbname, date, time, rows, sql FROM history"); + static_qstring(newHistoryInsert, "INSERT INTO sqleditor_history (id, dbname, date, time_spent, rows, sql) VALUES (?, ?, ?, ?, ?, ?)"); + + SqlQueryPtr insertResults; + SqlResultsRowPtr row; + SqlQueryPtr results = oldCfgDb->exec(oldHistoryQuery); + if (results->isError()) + { + notifyError(tr("Could not read SQL queries history from old configuration file in order to migrate it: %1").arg(results->getErrorText())); + return false; + } + + SqlQueryPtr idResults = newCfgDb->exec(historyIdQuery); + if (idResults->isError()) + { + notifyError(tr("Could not read next ID for SQL queries history in new configuration file: %1").arg(idResults->getErrorText())); + return false; + } + qint64 nextId = idResults->getSingleCell().toLongLong(); + + int timeSpent; + int date; + while (results->hasNext()) + { + row = results->next(); + timeSpent = qRound(row->value("time").toDouble() * 1000); + date = QDateTime::fromString(row->value("date").toString(), "yyyy-MM-dd HH:mm").toTime_t(); + + insertResults = newCfgDb->exec(newHistoryInsert, {nextId++, row->value("dbname"), date, timeSpent, row->value("rows"), row->value("sql")}); + if (insertResults->isError()) + { + notifyError(tr("Could not insert SQL history entry into new configuration file: %1").arg(insertResults->getErrorText())); + return false; + } + } + + return true; +} + +void ConfigMigrationWizard::finalize() +{ + if (checkedTypes.contains(ConfigMigrationItem::Type::FUNCTION_LIST)) + { + FUNCTIONS->setScriptFunctions(fnList); + fnList.clear(); + } + + if (checkedTypes.contains(ConfigMigrationItem::Type::SQL_HISTORY)) + CFG->refreshSqlHistory(); + + if (checkedTypes.contains(ConfigMigrationItem::Type::DATABASES)) + { + bool ignore = DBTREE->getModel()->getIgnoreDbLoadedSignal(); + DBTREE->getModel()->setIgnoreDbLoadedSignal(true); + DBLIST->scanForNewDatabasesInConfig(); + DBTREE->getModel()->setIgnoreDbLoadedSignal(ignore); + DBTREE->getModel()->loadDbList(); + } + + migrated = true; +} + +void ConfigMigrationWizard::collectCheckedTypes() +{ + checkedTypes.clear(); + + QTreeWidgetItem* item = nullptr; + for (int i = 0, total = ui->itemsTree->topLevelItemCount(); i < total; ++i) + { + item = ui->itemsTree->topLevelItem(i); + if (item->checkState(0) != Qt::Checked) + continue; + + checkedTypes << static_cast(item->data(0, Qt::UserRole).toInt()); + } +} + +void ConfigMigrationWizard::clearFunctions() +{ + for (FunctionManager::ScriptFunction* fn : fnList) + delete fn; + + fnList.clear(); +} + +void ConfigMigrationWizard::updateOptions() +{ + if (currentPage() == ui->optionsPage) + { + collectCheckedTypes(); + ui->dbGroup->setEnabled(checkedTypes.contains(ConfigMigrationItem::Type::DATABASES)); + } +} diff --git a/Plugins/ConfigMigration/configmigrationwizard.h b/Plugins/ConfigMigration/configmigrationwizard.h new file mode 100644 index 0000000..44a9f16 --- /dev/null +++ b/Plugins/ConfigMigration/configmigrationwizard.h @@ -0,0 +1,52 @@ +#ifndef CONFIGMIGRATIONWIZARD_H +#define CONFIGMIGRATIONWIZARD_H + +#include "configmigrationitem.h" +#include "services/functionmanager.h" +#include + +namespace Ui { +class ConfigMigrationWizard; +} + +class ConfigMigration; +class Db; + +class ConfigMigrationWizard : public QWizard +{ + Q_OBJECT + + public: + ConfigMigrationWizard(QWidget *parent, ConfigMigration* cfgMigration); + ~ConfigMigrationWizard(); + bool didMigrate(); + + private: + void init(); + void migrate(); + bool migrateSelected(Db* oldCfgDb, Db* newCfgDb); + bool migrateBugReports(Db* oldCfgDb, Db* newCfgDb); + bool migrateDatabases(Db* oldCfgDb, Db* newCfgDb); + bool migrateFunction(Db* oldCfgDb, Db* newCfgDb); + bool migrateSqlHistory(Db* oldCfgDb, Db* newCfgDb); + void finalize(); + void collectCheckedTypes(); + void clearFunctions(); + + Ui::ConfigMigrationWizard *ui = nullptr; + ConfigMigration* cfgMigration = nullptr; + QList checkedTypes; + QList fnList; + bool migrated = false; + + private slots: + void updateOptions(); + + public slots: + void accept(); + + signals: + void updateOptionsValidation(); +}; + +#endif // CONFIGMIGRATIONWIZARD_H diff --git a/Plugins/ConfigMigration/configmigrationwizard.ui b/Plugins/ConfigMigration/configmigrationwizard.ui new file mode 100644 index 0000000..ce35430 --- /dev/null +++ b/Plugins/ConfigMigration/configmigrationwizard.ui @@ -0,0 +1,109 @@ + + + ConfigMigrationWizard + + + + 0 + 0 + 392 + 282 + + + + Configuration migration + + + QTreeView::item {margin-top: 5px;} + + + QWizard::NoDefaultButton + + + + Items to migrate + + + This is a list of items found in the old configuration file, which can be migrated into the current configuration. + + + + + + 0 + + + false + + + false + + + false + + + false + + + + 1 + + + + + + + + + Options + + + + + + Put imported databases into separate group + + + true + + + false + + + + + + Group name + + + + + + + + + + Qt::Vertical + + + + 20 + 205 + + + + + + + + + + VerifiableWizardPage + QWizardPage +
common/verifiablewizardpage.h
+ 1 +
+
+ + +
diff --git a/Plugins/CsvExport/CsvExport.pro b/Plugins/CsvExport/CsvExport.pro new file mode 100644 index 0000000..9ffc8f9 --- /dev/null +++ b/Plugins/CsvExport/CsvExport.pro @@ -0,0 +1,29 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-06-14T16:04:07 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = CsvExport +TEMPLATE = lib + +DEFINES += CSVEXPORT_LIBRARY + +SOURCES += csvexport.cpp + +HEADERS += csvexport.h\ + csvexport_global.h + +FORMS += \ + CsvExport.ui + +OTHER_FILES += \ + csvexport.json + +RESOURCES += \ + csvexport.qrc + diff --git a/Plugins/CsvExport/CsvExport.ui b/Plugins/CsvExport/CsvExport.ui new file mode 100644 index 0000000..ec80b57 --- /dev/null +++ b/Plugins/CsvExport/CsvExport.ui @@ -0,0 +1,100 @@ + + + CsvExport + + + + 0 + 0 + 312 + 90 + + + + Form + + + + + + Column names in first row + + + CsvExport.ColumnsInFirstRow + + + + + + + Column separator: + + + + + + + CsvExport.Separator + + + + , (comma) + + + + + ; (semicolon) + + + + + \t (tab) + + + + + (whitespace) + + + + + Custom: + + + + + + + + + 30 + 16777215 + + + + CsvExport.CustomSeparator + + + + + + + Export NULL values as: + + + + + + + Empty string + + + CsvExport.NullValueString + + + + + + + + diff --git a/Plugins/CsvExport/csvexport.cpp b/Plugins/CsvExport/csvexport.cpp new file mode 100644 index 0000000..bdaf729 --- /dev/null +++ b/Plugins/CsvExport/csvexport.cpp @@ -0,0 +1,191 @@ +#include "csvexport.h" +#include "common/unused.h" +#include "services/exportmanager.h" +#include "csvserializer.h" + +CsvExport::CsvExport() +{ +} + +QString CsvExport::getFormatName() const +{ + return QStringLiteral("CSV"); +} + +ExportManager::StandardConfigFlags CsvExport::standardOptionsToEnable() const +{ + return ExportManager::CODEC; +} + +ExportManager::ExportModes CsvExport::getSupportedModes() const +{ + return ExportManager::FILE|ExportManager::TABLE|ExportManager::QUERY_RESULTS|ExportManager::CLIPBOARD; +} + +QString CsvExport::getExportConfigFormName() const +{ + return QStringLiteral("CsvExport"); +} + +CfgMain* CsvExport::getConfig() +{ + return &cfg; +} + +void CsvExport::validateOptions() +{ + if (cfg.CsvExport.Separator.get() >= 4) + { + EXPORT_MANAGER->updateVisibilityAndEnabled(cfg.CsvExport.CustomSeparator, true, true); + + bool valid = !cfg.CsvExport.CustomSeparator.get().isEmpty(); + EXPORT_MANAGER->handleValidationFromPlugin(valid, cfg.CsvExport.CustomSeparator, tr("Enter the custom separator character.")); + } + else + { + EXPORT_MANAGER->updateVisibilityAndEnabled(cfg.CsvExport.CustomSeparator, true, false); + EXPORT_MANAGER->handleValidationFromPlugin(true, cfg.CsvExport.CustomSeparator); + } +} + +QString CsvExport::defaultFileExtension() const +{ + return QStringLiteral("csv"); +} + +bool CsvExport::beforeExportQueryResults(const QString& query, QList& columns, const QHash providedData) +{ + UNUSED(query); + UNUSED(providedData); + defineCsvFormat(); + + QStringList cols; + for (QueryExecutor::ResultColumnPtr resCol : columns) + cols << resCol->displayName; + + writeln(CsvSerializer::serialize(cols, format)); + return true; +} + +bool CsvExport::exportQueryResultsRow(SqlResultsRowPtr row) +{ + exportTableRow(row); + return true; +} + +bool CsvExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, const QHash providedData) +{ + UNUSED(database); + UNUSED(table); + UNUSED(ddl); + UNUSED(providedData); + UNUSED(createTable); + return exportTable(columnNames); +} + +bool CsvExport::exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, const QHash providedData) +{ + UNUSED(database); + UNUSED(table); + UNUSED(ddl); + UNUSED(providedData); + UNUSED(createTable); + return exportTable(columnNames); +} + +bool CsvExport::exportTable(const QStringList& columnNames) +{ + if (!isTableExport()) + return false; + + defineCsvFormat(); + writeln(CsvSerializer::serialize(columnNames, format)); + return true; +} + +bool CsvExport::exportTableRow(SqlResultsRowPtr data) +{ + QStringList valList; + QString nl = cfg.CsvExport.NullValueString.get(); + for (const QVariant& val : data->valueList()) + valList << (val.isNull() ? nl : val.toString()); + + writeln(CsvSerializer::serialize(valList, format)); + return true; +} + +bool CsvExport::beforeExportDatabase(const QString& database) +{ + UNUSED(database); + return false; +} + +bool CsvExport::exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) +{ + UNUSED(database); + UNUSED(name); + UNUSED(ddl); + UNUSED(createIndex); + if (isTableExport()) + return true; + + return false; +} + +bool CsvExport::exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) +{ + UNUSED(database); + UNUSED(name); + UNUSED(ddl); + UNUSED(createTrigger); + if (isTableExport()) + return true; + + return false; +} + +bool CsvExport::exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr createView) +{ + UNUSED(database); + UNUSED(name); + UNUSED(ddl); + UNUSED(createView); + return false; +} + +void CsvExport::defineCsvFormat() +{ + format = CsvFormat(); + format.rowSeparator = '\n'; + + switch (cfg.CsvExport.Separator.get()) + { + case 0: + format.columnSeparator = ','; + break; + case 1: + format.columnSeparator = ';'; + break; + case 2: + format.columnSeparator = '\t'; + break; + case 3: + format.columnSeparator = ' '; + break; + default: + format.columnSeparator = cfg.CsvExport.CustomSeparator.get(); + break; + } +} + + +bool CsvExport::init() +{ + Q_INIT_RESOURCE(csvexport); + return GenericExportPlugin::init(); +} + +void CsvExport::deinit() +{ + Q_CLEANUP_RESOURCE(csvexport); +} diff --git a/Plugins/CsvExport/csvexport.h b/Plugins/CsvExport/csvexport.h new file mode 100644 index 0000000..7ecde7c --- /dev/null +++ b/Plugins/CsvExport/csvexport.h @@ -0,0 +1,56 @@ +#ifndef CSVEXPORT_H +#define CSVEXPORT_H + +#include "csvexport_global.h" +#include "plugins/genericexportplugin.h" +#include "config_builder.h" +#include "csvformat.h" + +CFG_CATEGORIES(CsvExportConfig, + CFG_CATEGORY(CsvExport, + CFG_ENTRY(bool, ColumnsInFirstRow, false) + CFG_ENTRY(int, Separator, 0) + CFG_ENTRY(QString, CustomSeparator, QString()) + CFG_ENTRY(QString, NullValueString, QString()) + ) +) + +class CSVEXPORTSHARED_EXPORT CsvExport : public GenericExportPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("csvexport.json") + + public: + CsvExport(); + + QString getFormatName() const; + ExportManager::StandardConfigFlags standardOptionsToEnable() const; + ExportManager::ExportModes getSupportedModes() const; + QString getExportConfigFormName() const; + CfgMain* getConfig(); + void validateOptions(); + QString defaultFileExtension() const; + bool beforeExportQueryResults(const QString& query, QList& columns, + const QHash providedData); + bool exportQueryResultsRow(SqlResultsRowPtr row); + bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash providedData); + bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, + const QHash providedData); + bool exportTableRow(SqlResultsRowPtr data); + bool beforeExportDatabase(const QString& database); + bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex); + bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger); + bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr createView); + bool init(); + void deinit(); + + private: + bool exportTable(const QStringList& columnNames); + void defineCsvFormat(); + + CFG_LOCAL(CsvExportConfig, cfg) + CsvFormat format; +}; + +#endif // CSVEXPORT_H diff --git a/Plugins/CsvExport/csvexport.json b/Plugins/CsvExport/csvexport.json new file mode 100644 index 0000000..57def5c --- /dev/null +++ b/Plugins/CsvExport/csvexport.json @@ -0,0 +1,7 @@ +{ + "type": "ExportPlugin", + "title": "CSV export", + "description": "Provides CSV format for exporting", + "version": 10000, + "author": "SalSoft" +} diff --git a/Plugins/CsvExport/csvexport.qrc b/Plugins/CsvExport/csvexport.qrc new file mode 100644 index 0000000..21d7213 --- /dev/null +++ b/Plugins/CsvExport/csvexport.qrc @@ -0,0 +1,5 @@ + + + CsvExport.ui + + diff --git a/Plugins/CsvExport/csvexport_global.h b/Plugins/CsvExport/csvexport_global.h new file mode 100644 index 0000000..8bdf173 --- /dev/null +++ b/Plugins/CsvExport/csvexport_global.h @@ -0,0 +1,12 @@ +#ifndef CSVEXPORT_GLOBAL_H +#define CSVEXPORT_GLOBAL_H + +#include + +#if defined(CSVEXPORT_LIBRARY) +# define CSVEXPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define CSVEXPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // CSVEXPORT_GLOBAL_H diff --git a/Plugins/CsvImport/CsvImport.pro b/Plugins/CsvImport/CsvImport.pro new file mode 100644 index 0000000..dfdb15b --- /dev/null +++ b/Plugins/CsvImport/CsvImport.pro @@ -0,0 +1,28 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-05-17T20:38:58 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = CsvImport +TEMPLATE = lib + +DEFINES += CSVIMPORT_LIBRARY + +SOURCES += csvimport.cpp + +HEADERS += csvimport.h\ + csvimport_global.h + +FORMS += \ + CsvImportOptions.ui + +OTHER_FILES += \ + csvimport.json + +RESOURCES += \ + csvimport.qrc diff --git a/Plugins/CsvImport/CsvImportOptions.ui b/Plugins/CsvImport/CsvImportOptions.ui new file mode 100644 index 0000000..5a1c6ab --- /dev/null +++ b/Plugins/CsvImport/CsvImportOptions.ui @@ -0,0 +1,103 @@ + + + csvImportOptions + + + + 0 + 0 + 333 + 90 + + + + Form + + + + + + CsvImport.Separator + + + + , (comma) + + + + + ; (semicolon) + + + + + \t (tab) + + + + + (whitespace) + + + + + Custom: + + + + + + + + CsvImport.FirstRowAsColumns + + + First row represents column names + + + + + + + Field separator: + + + + + + + + 30 + 16777215 + + + + CsvImport.CustomSeparator + + + + + + + CsvImport.NullValues + + + NULL values: + + + + + + + If your CSV data contains null values, define how are they represented in the CSV. + + + CsvImport.NullValueString + + + + + + + + diff --git a/Plugins/CsvImport/csvimport.cpp b/Plugins/CsvImport/csvimport.cpp new file mode 100644 index 0000000..cec81df --- /dev/null +++ b/Plugins/CsvImport/csvimport.cpp @@ -0,0 +1,206 @@ +#include "csvimport.h" +#include "services/importmanager.h" +#include "sqlitestudio.h" +#include "services/notifymanager.h" +#include +#include +#include + +CsvImport::CsvImport() +{ +} + +QString CsvImport::getDataSourceTypeName() const +{ + return "CSV"; +} + +ImportManager::StandardConfigFlags CsvImport::standardOptionsToEnable() const +{ + return ImportManager::CODEC|ImportManager::FILE_NAME; +} + +bool CsvImport::beforeImport(const ImportManager::StandardImportConfig& config) +{ + defineCsvFormat(); + + file = new QFile(config.inputFileName); + if (!file->open(QFile::ReadOnly) || !file->isReadable()) + { + notifyError(tr("Cannot read file %1").arg(config.inputFileName)); + safe_delete(file); + return false; + } + + stream = new QTextStream(file); + stream->setCodec(config.codec.toLatin1().data()); + + if (!extractColumns()) + { + safe_delete(stream); + safe_delete(file); + return false; + } + + return true; +} + +void CsvImport::afterImport() +{ + safe_delete(stream); + safe_delete(file); +} + +bool CsvImport::extractColumns() +{ + QString line = stream->readLine(); + while (line.trimmed().isEmpty() && !stream->atEnd()) + line = stream->readLine(); + + if (line.trimmed().isEmpty()) + { + notifyError(tr("Could not find any data in the file %1.").arg(file->fileName())); + return false; + } + + QStringList deserialized = CsvSerializer::deserialize(line.trimmed(), csvFormat).first(); + if (cfg.CsvImport.FirstRowAsColumns.get()) + { + columnNames = deserialized; + } + else + { + static const QString colTmp = QStringLiteral("column%1"); + columnNames.clear(); + for (int i = 1, total = deserialized.size(); i <= total; ++i) + columnNames << colTmp.arg(i); + + stream->seek(0); + } + + return true; +} + +void CsvImport::defineCsvFormat() +{ + csvFormat = CsvFormat(); + csvFormat.rowSeparator = '\n'; + + switch (cfg.CsvImport.Separator.get()) + { + case 0: + csvFormat.columnSeparator = ','; + break; + case 1: + csvFormat.columnSeparator = ';'; + break; + case 2: + csvFormat.columnSeparator = '\t'; + break; + case 3: + csvFormat.columnSeparator = ' '; + break; + default: + csvFormat.columnSeparator = cfg.CsvImport.CustomSeparator.get(); + break; + } +} + +QList CsvImport::getColumns() const +{ + QList columnList; + for (const QString& colName : columnNames) + columnList << ImportPlugin::ColumnDefinition(colName, QString()); + + return columnList; +} + +QList CsvImport::next() +{ + QString line = stream->readLine(); + if (line.isNull()) + return QList(); + + QList values; + QList deserialized = CsvSerializer::deserialize(line, csvFormat); + if (deserialized.size() > 0) + { + if (cfg.CsvImport.NullValues.get()) + { + QString nullVal = cfg.CsvImport.NullValueString.get(); + for (const QString& val : deserialized.first()) + { + if (val == nullVal) + values << QVariant(QVariant::String); + else + values << val; + } + } + else + { + for (const QString& val : deserialized.first()) + values << val; + } + } + + return values; +} + +CfgMain* CsvImport::getConfig() +{ + return &cfg; +} + +QString CsvImport::getImportConfigFormName() const +{ + return "csvImportOptions"; +} + +bool CsvImport::validateOptions() +{ + bool isValid = true; + if (cfg.CsvImport.Separator.get() >= 4) + { + IMPORT_MANAGER->updateVisibilityAndEnabled(cfg.CsvImport.CustomSeparator, true, true); + + bool valid = !cfg.CsvImport.CustomSeparator.get().isEmpty(); + IMPORT_MANAGER->handleValidationFromPlugin(valid, cfg.CsvImport.CustomSeparator, tr("Enter the custom separator character.")); + isValid &= valid; + } + else + { + IMPORT_MANAGER->updateVisibilityAndEnabled(cfg.CsvImport.CustomSeparator, true, false); + IMPORT_MANAGER->handleValidationFromPlugin(true, cfg.CsvImport.CustomSeparator); + } + + if (cfg.CsvImport.NullValues.get()) + { + IMPORT_MANAGER->updateVisibilityAndEnabled(cfg.CsvImport.NullValueString, true, true); + + bool valid = !cfg.CsvImport.NullValueString.get().isEmpty(); + IMPORT_MANAGER->handleValidationFromPlugin(valid, cfg.CsvImport.NullValueString, tr("Enter the value that will be interpreted as a NULL.")); + isValid &= valid; + } + else + { + IMPORT_MANAGER->updateVisibilityAndEnabled(cfg.CsvImport.NullValueString, true, false); + IMPORT_MANAGER->handleValidationFromPlugin(true, cfg.CsvImport.NullValueString); + } + return isValid; +} + +QString CsvImport::getFileFilter() const +{ + return tr("CSV files (*.csv);;Text files (*.txt);;All files (*)"); +} + +bool CsvImport::init() +{ + Q_INIT_RESOURCE(csvimport); + return GenericPlugin::init(); +} + +void CsvImport::deinit() +{ + Q_CLEANUP_RESOURCE(csvimport); +} diff --git a/Plugins/CsvImport/csvimport.h b/Plugins/CsvImport/csvimport.h new file mode 100644 index 0000000..cec803c --- /dev/null +++ b/Plugins/CsvImport/csvimport.h @@ -0,0 +1,55 @@ +#ifndef CSVIMPORT_H +#define CSVIMPORT_H + +#include "csvimport_global.h" +#include "plugins/importplugin.h" +#include "plugins/genericplugin.h" +#include "config_builder.h" +#include "csvserializer.h" + +CFG_CATEGORIES(CsvImportConfig, + CFG_CATEGORY(CsvImport, + CFG_ENTRY(bool, FirstRowAsColumns, false) + CFG_ENTRY(int, Separator, 0) + CFG_ENTRY(QString, CustomSeparator, QString()) + CFG_ENTRY(bool, NullValues, false) + CFG_ENTRY(QString, NullValueString, QString()) + ) +) + +class QFile; +class QTextStream; + +class CSVIMPORTSHARED_EXPORT CsvImport : public GenericPlugin, public ImportPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("csvimport.json") + + public: + CsvImport(); + + QString getDataSourceTypeName() const; + ImportManager::StandardConfigFlags standardOptionsToEnable() const; + bool beforeImport(const ImportManager::StandardImportConfig& config); + void afterImport(); + QList getColumns() const; + QList next(); + CfgMain* getConfig(); + QString getImportConfigFormName() const; + bool validateOptions(); + QString getFileFilter() const; + bool init(); + void deinit(); + + private: + bool extractColumns(); + void defineCsvFormat(); + + QFile* file = nullptr; + QTextStream* stream = nullptr; + QStringList columnNames; + CsvFormat csvFormat; + CFG_LOCAL(CsvImportConfig, cfg) +}; + +#endif // CSVIMPORT_H diff --git a/Plugins/CsvImport/csvimport.json b/Plugins/CsvImport/csvimport.json new file mode 100644 index 0000000..016c539 --- /dev/null +++ b/Plugins/CsvImport/csvimport.json @@ -0,0 +1,7 @@ +{ + "type": "ImportPlugin", + "title": "CSV import", + "description": "CSV format support for importing data", + "version": 10000, + "author": "SalSoft" +} diff --git a/Plugins/CsvImport/csvimport.qrc b/Plugins/CsvImport/csvimport.qrc new file mode 100644 index 0000000..a071122 --- /dev/null +++ b/Plugins/CsvImport/csvimport.qrc @@ -0,0 +1,5 @@ + + + CsvImportOptions.ui + + diff --git a/Plugins/CsvImport/csvimport_global.h b/Plugins/CsvImport/csvimport_global.h new file mode 100644 index 0000000..cee5321 --- /dev/null +++ b/Plugins/CsvImport/csvimport_global.h @@ -0,0 +1,12 @@ +#ifndef CSVIMPORT_GLOBAL_H +#define CSVIMPORT_GLOBAL_H + +#include + +#if defined(CSVIMPORT_LIBRARY) +# define CSVIMPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define CSVIMPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // CSVIMPORT_GLOBAL_H diff --git a/Plugins/DbSqlite2/DbSqlite2.pro b/Plugins/DbSqlite2/DbSqlite2.pro new file mode 100644 index 0000000..656bd5d --- /dev/null +++ b/Plugins/DbSqlite2/DbSqlite2.pro @@ -0,0 +1,26 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-04-08T22:41:09 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = DbSqlite2 +TEMPLATE = lib + +DEFINES += DBSQLITE2_LIBRARY + +SOURCES += dbsqlite2.cpp \ + dbsqlite2instance.cpp + +HEADERS += dbsqlite2.h\ + dbsqlite2_global.h \ + dbsqlite2instance.h + +LIBS += -lsqlite + +OTHER_FILES += \ + dbsqlite2.json diff --git a/Plugins/DbSqlite2/dbsqlite2.cpp b/Plugins/DbSqlite2/dbsqlite2.cpp new file mode 100644 index 0000000..8047c7e --- /dev/null +++ b/Plugins/DbSqlite2/dbsqlite2.cpp @@ -0,0 +1,51 @@ +#include "dbsqlite2.h" +#include "dbsqlite2instance.h" +#include "common/unused.h" +#include + +DbSqlite2::DbSqlite2() +{ +} + +Db* DbSqlite2::getInstance(const QString& name, const QString& path, const QHash& options, QString* errorMessage) +{ + UNUSED(errorMessage); + Db* db = new DbSqlite2Instance(name, path, options); + + if (!db->openForProbing()) + { + delete db; + return nullptr; + } + + SqlQueryPtr results = db->exec("SELECT * FROM sqlite_master"); + if (results->isError()) + { + delete db; + return nullptr; + } + + db->closeQuiet(); + return db; +} + +QList DbSqlite2::getOptionsList() const +{ + return QList(); +} + +QString DbSqlite2::generateDbName(const QVariant& baseValue) +{ + QFileInfo file(baseValue.toString()); + return file.baseName(); +} + +QString DbSqlite2::getLabel() const +{ + return "SQLite 2"; +} + +bool DbSqlite2::checkIfDbServedByPlugin(Db* db) const +{ + return (db && dynamic_cast(db)); +} diff --git a/Plugins/DbSqlite2/dbsqlite2.h b/Plugins/DbSqlite2/dbsqlite2.h new file mode 100644 index 0000000..c428031 --- /dev/null +++ b/Plugins/DbSqlite2/dbsqlite2.h @@ -0,0 +1,23 @@ +#ifndef DBSQLITE2_H +#define DBSQLITE2_H + +#include "dbsqlite2_global.h" +#include "plugins/dbplugin.h" +#include "plugins/genericplugin.h" + +class DBSQLITE2SHARED_EXPORT DbSqlite2 : public GenericPlugin, public DbPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("dbsqlite2.json") + + public: + DbSqlite2(); + + QString getLabel() const; + bool checkIfDbServedByPlugin(Db* db) const; + Db* getInstance(const QString& name, const QString& path, const QHash& options, QString* errorMessage); + QList getOptionsList() const; + QString generateDbName(const QVariant& baseValue); +}; + +#endif // DBSQLITE2_H diff --git a/Plugins/DbSqlite2/dbsqlite2.json b/Plugins/DbSqlite2/dbsqlite2.json new file mode 100644 index 0000000..275a794 --- /dev/null +++ b/Plugins/DbSqlite2/dbsqlite2.json @@ -0,0 +1,7 @@ +{ + "type": "DbPlugin", + "title": "SQLite 2", + "description": "Provides support for SQLite 2.* databases", + "version": 10001, + "author": "SalSoft" +} diff --git a/Plugins/DbSqlite2/dbsqlite2_global.h b/Plugins/DbSqlite2/dbsqlite2_global.h new file mode 100644 index 0000000..42b7af8 --- /dev/null +++ b/Plugins/DbSqlite2/dbsqlite2_global.h @@ -0,0 +1,12 @@ +#ifndef DBSQLITE2_GLOBAL_H +#define DBSQLITE2_GLOBAL_H + +#include + +#if defined(DBSQLITE2_LIBRARY) +# define DBSQLITE2SHARED_EXPORT Q_DECL_EXPORT +#else +# define DBSQLITE2SHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // DBSQLITE2_GLOBAL_H diff --git a/Plugins/DbSqlite2/dbsqlite2instance.cpp b/Plugins/DbSqlite2/dbsqlite2instance.cpp new file mode 100644 index 0000000..905f6fe --- /dev/null +++ b/Plugins/DbSqlite2/dbsqlite2instance.cpp @@ -0,0 +1,7 @@ +#include "dbsqlite2instance.h" + +DbSqlite2Instance::DbSqlite2Instance(const QString& name, const QString& path, const QHash& connOptions) : + AbstractDb2(name, path, connOptions) +{ + +} diff --git a/Plugins/DbSqlite2/dbsqlite2instance.h b/Plugins/DbSqlite2/dbsqlite2instance.h new file mode 100644 index 0000000..83c54d3 --- /dev/null +++ b/Plugins/DbSqlite2/dbsqlite2instance.h @@ -0,0 +1,27 @@ +#ifndef DBSQLITE2INSTANCE_H +#define DBSQLITE2INSTANCE_H + +#include "db/abstractdb2.h" +#include "common/global.h" +#include + +struct Sqlite2 +{ + static_char* label = "SQLite 2"; +}; + +class DbSqlite2Instance : public AbstractDb2 +{ + public: + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See AbstractDb for details. + * + * All values from this constructor are just passed to AbstractDb2 constructor. + */ + DbSqlite2Instance(const QString& name, const QString& path, const QHash& connOptions); +}; + +#endif // DBSQLITE2INSTANCE_H diff --git a/Plugins/HtmlExport/HtmlExport.pro b/Plugins/HtmlExport/HtmlExport.pro new file mode 100644 index 0000000..ff0a68e --- /dev/null +++ b/Plugins/HtmlExport/HtmlExport.pro @@ -0,0 +1,29 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-06-20T22:57:37 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = HtmlExport +TEMPLATE = lib + +DEFINES += HTMLEXPORT_LIBRARY + +SOURCES += htmlexport.cpp + +HEADERS += htmlexport.h\ + htmlexport_global.h + +OTHER_FILES += \ + htmlexport.json \ + htmlexport.css + +RESOURCES += \ + htmlexport.qrc + +FORMS += \ + htmlexport.ui diff --git a/Plugins/HtmlExport/htmlexport.cpp b/Plugins/HtmlExport/htmlexport.cpp new file mode 100644 index 0000000..75b3868 --- /dev/null +++ b/Plugins/HtmlExport/htmlexport.cpp @@ -0,0 +1,615 @@ +#include "htmlexport.h" +#include "services/pluginmanager.h" +#include "common/unused.h" +#include +#include +#include +#include +#include + +QString HtmlExport::getFormatName() const +{ + return "HTML"; +} + +ExportManager::StandardConfigFlags HtmlExport::standardOptionsToEnable() const +{ + return ExportManager::CODEC; +} + +QString HtmlExport::getExportConfigFormName() const +{ + return "HtmlExportConfig"; +} + +void HtmlExport::validateOptions() +{ + bool header = cfg.HtmlExport.PrintHeader.get(); + EXPORT_MANAGER->updateVisibilityAndEnabled(cfg.HtmlExport.PrintDataTypes, true, header); +} + +QString HtmlExport::defaultFileExtension() const +{ + return "html"; +} + +CfgMain* HtmlExport::getConfig() +{ + return &cfg; +} + +bool HtmlExport::beforeExportQueryResults(const QString& query, QList& columns, const QHash providedData) +{ + UNUSED(query); + UNUSED(providedData); + + if (!beginDoc(tr("SQL query results"))) + return false; + + columnTypes = QueryExecutor::resolveColumnTypes(db, columns, true); + + writeln(""); + incrIndent(); + if (printHeader) + { + writeln(""); + incrIndent(); + if (printRownum) + { + writeln(""); + } + + QString column; + int i = 0; + for (const QueryExecutor::ResultColumnPtr& col : columns) + { + writeln(""); + i++; + } + decrIndent(); + writeln(""); + } + + currentDataRow = 0; + return true; +} + +bool HtmlExport::exportQueryResultsRow(SqlResultsRowPtr row) +{ + return exportDataRow(row); +} + +bool HtmlExport::afterExportQueryResults() +{ + decrIndent(); + writeln("
"); + incrIndent(); + writeln("#"); + decrIndent(); + writeln(""); + incrIndent(); + column = QString("%1").arg(col->displayName); + if (printDatatypes) + { + if (!columnTypes[i].isNull()) + column.append("
" + columnTypes[i].toFullTypeString()); + else + column.append("
" + tr("no type")); + } + writeln(column); + decrIndent(); + writeln("
"); + writeln("

"); + return true; +} + +bool HtmlExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, const QHash providedData) +{ + UNUSED(database); + UNUSED(ddl); + UNUSED(columnNames); + UNUSED(providedData); + + if (isTableExport()) + { + if (!beginDoc(tr("Exported table: %1").arg(table))) + return false; + } + + int colCount = createTable->columns.size(); + int colSpan = printRownum ? colCount + 1 : colCount; + + writeln(""); + incrIndent(); + + writeln(""); + incrIndent(); + writeln(QString("").arg(colSpan).arg(tr("Table: %1").arg(table))); + decrIndent(); + writeln(""); + + if (printHeader) + { + writeln(""); + incrIndent(); + if (printRownum) + { + writeln(""); + } + + QString column; + for (SqliteCreateTable::Column* col : createTable->columns) + { + writeln(""); + } + decrIndent(); + writeln(""); + } + + columnTypes.clear(); + for (SqliteCreateTable::Column* col : createTable->columns) + { + if (col->type) + columnTypes << col->type->toDataType(); + else + columnTypes << DataType(); + } + + currentDataRow = 0; + return true; +} + +bool HtmlExport::exportDataRow(SqlResultsRowPtr data) +{ + currentDataRow++; + + writeln(""); + incrIndent(); + if (printRownum) + { + writeln(""); + } + + QString align; + QString cellValue; + QString cellStyle; + int i = 0; + for (const QVariant& value : data->valueList()) + { + if (columnTypes[i].isNumeric()) + align = "right"; + else + align = "left"; + + if (value.isNull()) + { + cellValue = "NULL"; + cellStyle = " class=\"null\""; + } + else + { + cellStyle = ""; + if (value.toString().trimmed().isEmpty()) + cellValue = " "; + else + { + cellValue = value.toString(); + cellValue.truncate(byteLengthLimit); + cellValue = escape(cellValue); + } + } + writeln(QString(""); + i++; + } + decrIndent(); + writeln(""); + return true; +} + +bool HtmlExport::exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, const QHash providedData) +{ + UNUSED(database); + UNUSED(ddl); + UNUSED(columnNames); + UNUSED(createTable); + UNUSED(providedData); + + if (isTableExport()) + { + if (!beginDoc(tr("Exported table: %1").arg(table))) + return false; + } + + int colCount = columnNames.size(); + int colSpan = printRownum ? colCount + 1 : colCount; + + writeln("
%2
"); + incrIndent(); + writeln("#"); + decrIndent(); + writeln(""); + incrIndent(); + column = QString("%1").arg(col->name); + if (printDatatypes) + { + if (col->type) + column.append("
" + col->type->toDataType().toFullTypeString()); + else + column.append("
" + tr("no type")); + } + writeln(column); + decrIndent(); + writeln("
"); + incrIndent(); + writeln(QString("%1").arg(currentDataRow)); + decrIndent(); + writeln("").arg(align, cellStyle)); + incrIndent(); + writeln(cellValue); + decrIndent(); + writeln("
"); + incrIndent(); + + writeln(""); + incrIndent(); + writeln(QString("").arg(colSpan).arg(tr("Table: %1").arg(table), tr("virtual"))); + decrIndent(); + writeln(""); + + if (printHeader) + { + writeln(""); + incrIndent(); + if (printRownum) + { + writeln(""); + } + + QString column; + for (const QString& col : columnNames) + { + writeln(""); + } + decrIndent(); + writeln(""); + } + + columnTypes.clear(); + for (int i = 0, total = columnNames.size(); i < total; ++i) + columnTypes << DataType(); + + currentDataRow = 0; + return true; +} + +bool HtmlExport::exportTableRow(SqlResultsRowPtr data) +{ + return exportDataRow(data); +} + +bool HtmlExport::afterExportTable() +{ + decrIndent(); + writeln("
%2 (%3)
"); + incrIndent(); + writeln("#"); + decrIndent(); + writeln(""); + incrIndent(); + writeln(QString("%1").arg(col)); + decrIndent(); + writeln("
"); + writeln("

"); + return true; +} + +bool HtmlExport::beforeExportDatabase(const QString& database) +{ + if (!beginDoc(tr("Exported database: %1").arg(database))) + return false; + + return true; +} + +bool HtmlExport::exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) +{ + UNUSED(database); + UNUSED(ddl); + + writeln(""); + incrIndent(); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("Index: %1").arg("" + name + ""))); + decrIndent(); + writeln(""); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("For table:"))); + writeln(QString("").arg(createIndex->table)); + decrIndent(); + writeln(""); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("Unique:"))); + writeln(QString("").arg(createIndex->uniqueKw ? tr("Yes") : tr("No"))); + decrIndent(); + writeln(""); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("Column"))); + writeln(QString("").arg(tr("Collating"))); + writeln(QString("").arg(tr("Sort order"))); + decrIndent(); + writeln(""); + + for (SqliteIndexedColumn* idxCol : createIndex->indexedColumns) + { + writeln(""); + incrIndent(); + writeln(QString("").arg(escape(idxCol->name))); + writeln(QString("").arg(idxCol->collate.isNull() ? " " : escape(idxCol->collate))); + writeln(QString("").arg(idxCol->sortOrder == SqliteSortOrder::null ? " " : sqliteSortOrder(idxCol->sortOrder))); + decrIndent(); + writeln(""); + } + + decrIndent(); + writeln("
%1
%1%1
%1%1
%1%1%1
%1%1%1
"); + writeln("

"); + return true; +} + +bool HtmlExport::exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) +{ + UNUSED(database); + UNUSED(ddl); + + writeln(""); + incrIndent(); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("Trigger: %1").arg("" + name + ""))); + decrIndent(); + writeln(""); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("Activated:"))); + writeln(QString("").arg(SqliteCreateTrigger::time(createTrigger->eventTime))); + decrIndent(); + writeln(""); + + QString event = createTrigger->event ? SqliteCreateTrigger::Event::typeToString(createTrigger->event->type) : ""; + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("Action:"))); + writeln(QString("").arg(event)); + decrIndent(); + writeln(""); + + QString onObj; + if (createTrigger->eventTime == SqliteCreateTrigger::Time::INSTEAD_OF) + onObj = tr("On view:"); + else + onObj = tr("On table:"); + + writeln(""); + incrIndent(); + writeln(QString("").arg(onObj)); + writeln(QString("").arg(createTrigger->table)); + decrIndent(); + writeln(""); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("Activate condition:"))); + writeln(QString("").arg(createTrigger->precondition ? escape(createTrigger->precondition->detokenize()) : "")); + decrIndent(); + writeln(""); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("Code executed:"))); + decrIndent(); + writeln(""); + + QStringList queryStrings; + for (SqliteQuery* q : createTrigger->queries) + queryStrings << escape(q->detokenize()); + + writeln(""); + incrIndent(); + writeln(""); + decrIndent(); + writeln(""); + + decrIndent(); + writeln("
%1
%1%1
%1%1
%1%1
%1%1
%1
"); + incrIndent(); + writeln(QString("
%1
").arg(queryStrings.join("
"))); + decrIndent(); + writeln("
"); + writeln("

"); + return true; +} + +bool HtmlExport::exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view) +{ + UNUSED(database); + UNUSED(ddl); + + writeln(""); + incrIndent(); + + writeln(""); + incrIndent(); + writeln(QString("").arg(tr("View: %1").arg("" + name + ""))); + decrIndent(); + writeln(""); + + writeln(""); + incrIndent(); + writeln(""); + decrIndent(); + writeln(""); + + decrIndent(); + writeln("
%1
"); + incrIndent(); + writeln(QString("
%1
").arg(escape(view->select->detokenize()))); + decrIndent(); + writeln("
"); + writeln("

"); + return true; +} + +bool HtmlExport::afterExport() +{ + static const QString bodyEndTpl = QStringLiteral(""); + static const QString docEnd = QStringLiteral(""); + + writeln("" + tr("Document generated by SQLiteStudio v%1 on %2").arg(SQLITESTUDIO->getVersionString(), QDateTime::currentDateTime().toString()) + ""); + decrIndent(); + writeln(bodyEndTpl); + decrIndent(); + writeln(docEnd); + return true; +} + +bool HtmlExport::beginDoc(const QString& title) +{ + static const QString docStart = QStringLiteral( + R"()" + "\n" + ); + + static const QString metaCodecTpl = QStringLiteral(R"()"); + static const QString titletpl = QStringLiteral(R"(%1)"); + static const QString styleStartTpl = QStringLiteral(R"()"); + static const QString bodyStartTpl = QStringLiteral(R"()"); + + setupConfig(); + + QFile file(":/htmlexport/htmlexport.css"); + if (!file.open(QIODevice::ReadOnly)) + { + qCritical() << "Could not open htmlexport.css resource while exporting to HTML:" << file.errorString(); + return false; + } + + writeln(docStart); + incrIndent(); + writeln(metaCodecTpl.arg(codecName)); + writeln(titletpl.arg(title)); + writeln(styleStartTpl); + incrIndent(); + writeln(indent ? file.readAll() : compressCss(file.readAll())); + decrIndent(); + writeln(styleEndTpl); + writeln(bodyStartTpl); + incrIndent(); + + file.close(); + return true; +} + +void HtmlExport::setupConfig() +{ + codecName = codec->name(); + indentDepth = 0; + newLineStr = ""; + indentStr = ""; + indent = (cfg.HtmlExport.Format.get() == "format"); + if (indent) + newLineStr = "\n"; + + printRownum = cfg.HtmlExport.PrintRowNum.get(); + printHeader = cfg.HtmlExport.PrintHeader.get(); + printDatatypes = printHeader && cfg.HtmlExport.PrintDataTypes.get(); + byteLengthLimit = cfg.HtmlExport.ByteLengthLimit.get(); +} + +void HtmlExport::incrIndent() +{ + if (indent) + { + indentDepth++; + updateIndent(); + } +} + +void HtmlExport::decrIndent() +{ + if (indent) + { + indentDepth--; + updateIndent(); + } +} + +void HtmlExport::updateIndent() +{ + indentStr = QString(" ").repeated(indentDepth); +} + +void HtmlExport::writeln(const QString& str) +{ + QString newStr; + if (str.contains("\n")) + { + QStringList lines = str.split("\n"); + QMutableStringListIterator it(lines); + while (it.hasNext()) + it.next().prepend(indentStr); + + newStr = lines.join("\n") + newLineStr; + } + else + { + newStr = indentStr + str + newLineStr; + } + GenericExportPlugin::write(newStr); +} + +QString HtmlExport::escape(const QString& str) +{ + if (cfg.HtmlExport.DontEscapeHtml.get()) + return str; + + return str.toHtmlEscaped(); +} + +QString HtmlExport::compressCss(QString css) +{ + static const QRegExp spacesLeftRe(R"REGEXP(([^a-zA-Z0-9_\s]+)\s+(\S+))REGEXP"); + static const QRegExp spacesRightRe(R"REGEXP((\S+)\s+([^a-zA-Z0-9_\s]+))REGEXP"); + static const QRegExp spacesBetweenWordsRe(R"REGEXP((\S+)\s{2,}(\S+))REGEXP"); + while (spacesLeftRe.indexIn(css) > -1) + css.replace(spacesLeftRe, R"(\1\2)"); + + while (spacesRightRe.indexIn(css) > -1) + css.replace(spacesRightRe, R"(\1\2)"); + + while (spacesBetweenWordsRe.indexIn(css) > -1) + css.replace(spacesBetweenWordsRe, R"(\1 \2)"); + + return css.trimmed(); +} + +bool HtmlExport::init() +{ + Q_INIT_RESOURCE(htmlexport); + return GenericExportPlugin::init(); +} + +void HtmlExport::deinit() +{ + Q_CLEANUP_RESOURCE(htmlexport); +} diff --git a/Plugins/HtmlExport/htmlexport.css b/Plugins/HtmlExport/htmlexport.css new file mode 100644 index 0000000..72ebf4f --- /dev/null +++ b/Plugins/HtmlExport/htmlexport.css @@ -0,0 +1,59 @@ +table +{ + border-style: solid; + border-width: 1px; + border-color: black; + border-collapse: collapse; +} + +table tr +{ + background-color: white; +} + +table tr.header +{ + background-color: #DDDDDD; +} + +table tr.title +{ + background-color: #EEEEEE; +} + +table tr td +{ + padding: 0px 3px 0px 3px; + border-style: solid; + border-width: 1px; + border-color: #666666; +} + +table tr td.null +{ + color: #999999; + text-align: center; + padding: 0px 3px 0px 3px; + border-style: solid; + border-width: 1px; + border-color: #666666; +} + +table tr td.separator +{ + padding: 0px 3px 0px 3px; + border-style: solid; + border-width: 1px; + border-color: #666666; + background-color: #DDDDDD; +} + +table tr td.rownum +{ + padding: 0px 3px 0px 3px; + border-style: solid; + border-width: 1px; + border-color: #666666; + background-color: #DDDDDD; + text-align: right; +} diff --git a/Plugins/HtmlExport/htmlexport.h b/Plugins/HtmlExport/htmlexport.h new file mode 100644 index 0000000..04a379c --- /dev/null +++ b/Plugins/HtmlExport/htmlexport.h @@ -0,0 +1,74 @@ +#ifndef HTMLEXPORT_H +#define HTMLEXPORT_H + +#include "htmlexport_global.h" +#include "plugins/genericexportplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(HtmlExportConfig, + CFG_CATEGORY(HtmlExport, + CFG_ENTRY(QString, Format, "compress") + CFG_ENTRY(bool, PrintRowNum, true) + CFG_ENTRY(bool, PrintHeader, true) + CFG_ENTRY(bool, PrintDataTypes, true) + CFG_ENTRY(bool, DontEscapeHtml, false) + CFG_ENTRY(int, ByteLengthLimit, 10000) + ) +) +class HTMLEXPORTSHARED_EXPORT HtmlExport : public GenericExportPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("htmlexport.json") + + public: + QString getFormatName() const; + ExportManager::StandardConfigFlags standardOptionsToEnable() const; + QString getExportConfigFormName() const; + CfgMain* getConfig(); + void validateOptions(); + QString defaultFileExtension() const; + bool beforeExportQueryResults(const QString& query, QList& columns, + const QHash providedData); + bool exportQueryResultsRow(SqlResultsRowPtr row); + bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash providedData); + bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, + const QHash providedData); + bool exportTableRow(SqlResultsRowPtr data); + bool beforeExportDatabase(const QString& database); + bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex); + bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger); + bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view); + bool afterExportQueryResults(); + bool afterExportTable(); + bool afterExport(); + bool init(); + void deinit(); + + private: + bool beginDoc(const QString& title); + bool exportDataRow(SqlResultsRowPtr data); + void setupConfig(); + void incrIndent(); + void decrIndent(); + void updateIndent(); + void writeln(const QString& str); + QString escape(const QString& str); + + static QString compressCss(QString css); + + CFG_LOCAL(HtmlExportConfig, cfg) + bool indent = false; + int indentDepth = 0; + QString indentStr; + QString newLineStr; + QString codecName; + int currentDataRow = 0; + QList columnTypes; + bool printRownum = false; + bool printHeader = false; + bool printDatatypes = false; + int byteLengthLimit = 0; +}; + +#endif // HTMLEXPORT_H diff --git a/Plugins/HtmlExport/htmlexport.json b/Plugins/HtmlExport/htmlexport.json new file mode 100644 index 0000000..530a21d --- /dev/null +++ b/Plugins/HtmlExport/htmlexport.json @@ -0,0 +1,7 @@ +{ + "type": "ExportPlugin", + "title": "HTML export", + "description": "Provides HTML format for exporting.", + "version": 10000, + "author": "SalSoft" +} diff --git a/Plugins/HtmlExport/htmlexport.qrc b/Plugins/HtmlExport/htmlexport.qrc new file mode 100644 index 0000000..77f9482 --- /dev/null +++ b/Plugins/HtmlExport/htmlexport.qrc @@ -0,0 +1,8 @@ + + + htmlexport.css + + + htmlexport.ui + + diff --git a/Plugins/HtmlExport/htmlexport.ui b/Plugins/HtmlExport/htmlexport.ui new file mode 100644 index 0000000..a4727a4 --- /dev/null +++ b/Plugins/HtmlExport/htmlexport.ui @@ -0,0 +1,129 @@ + + + HtmlExportConfig + + + + 0 + 0 + 400 + 228 + + + + Form + + + + + + + 100 + 16777215 + + + + 999999999 + + + HtmlExport.ByteLengthLimit + + + + + + + Maximum number of characters per cell: + + + + + + + Include data types in first row + + + HtmlExport.PrintDataTypes + + + + + + + Column names as first row + + + HtmlExport.PrintHeader + + + + + + + Row numbers as first column + + + HtmlExport.PrintRowNum + + + + + + + Output format + + + + + + Format document (new lines, indentation) + + + HtmlExport.Format + + + format + + + + + + + Compress (everything in one line) + + + HtmlExport.Format + + + compress + + + + + + + + + + <p>When enabled, HTML characters such as &lt;, &gt; and &amp; are not escaped in exported values. This allows you for example to export hyper-link enabled documents, but it also may result in incorrect HTML document (unmatched pairs of &lt; and &gt; characters). Be warned.</p> + + + Don't escape HTML characters + + + HtmlExport.DontEscapeHtml + + + + + + + + ConfigRadioButton + QRadioButton +
common/configradiobutton.h
+
+
+ + +
diff --git a/Plugins/HtmlExport/htmlexport_global.h b/Plugins/HtmlExport/htmlexport_global.h new file mode 100644 index 0000000..02671c4 --- /dev/null +++ b/Plugins/HtmlExport/htmlexport_global.h @@ -0,0 +1,12 @@ +#ifndef HTMLEXPORT_GLOBAL_H +#define HTMLEXPORT_GLOBAL_H + +#include + +#if defined(HTMLEXPORT_LIBRARY) +# define HTMLEXPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define HTMLEXPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // HTMLEXPORT_GLOBAL_H diff --git a/Plugins/JsonExport/JsonExport.pro b/Plugins/JsonExport/JsonExport.pro new file mode 100644 index 0000000..8e29fcf --- /dev/null +++ b/Plugins/JsonExport/JsonExport.pro @@ -0,0 +1,28 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-07-16T20:04:29 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = JsonExport +TEMPLATE = lib + +DEFINES += JSONEXPORT_LIBRARY + +SOURCES += jsonexport.cpp + +HEADERS += jsonexport.h\ + jsonexport_global.h + +OTHER_FILES += \ + jsonexport.json + +FORMS += \ + jsonexport.ui + +RESOURCES += \ + jsonexport.qrc diff --git a/Plugins/JsonExport/jsonexport.cpp b/Plugins/JsonExport/jsonexport.cpp new file mode 100644 index 0000000..51fd60a --- /dev/null +++ b/Plugins/JsonExport/jsonexport.cpp @@ -0,0 +1,453 @@ +#include "jsonexport.h" +#include "common/unused.h" +#include + +JsonExport::JsonExport() +{ +} + +QString JsonExport::getFormatName() const +{ + return "JSON"; +} + +ExportManager::StandardConfigFlags JsonExport::standardOptionsToEnable() const +{ + return ExportManager::CODEC; +} + +QString JsonExport::getDefaultEncoding() const +{ + return "UTF-8"; +} + +QString JsonExport::getExportConfigFormName() const +{ + return "JsonExportConfig"; +} + +CfgMain* JsonExport::getConfig() +{ + return &cfg; +} + +void JsonExport::validateOptions() +{ +} + +QString JsonExport::defaultFileExtension() const +{ + return "json"; +} + +bool JsonExport::beforeExportQueryResults(const QString& query, QList& columns, const QHash providedData) +{ + UNUSED(providedData); + + beginObject(); + writeValue("type", "query results"); + writeValue("query", query); + + beginArray("columns"); + QList columnTypes = QueryExecutor::resolveColumnTypes(db, columns, true); + int i = 0; + for (QueryExecutor::ResultColumnPtr col : columns) + { + DataType& type = columnTypes[i]; + + beginObject(); + writeValue("displayName", col->displayName); + writeValue("name", col->column); + writeValue("database", col->database); + writeValue("table", col->table); + writeValue("type", type.toFullTypeString()); + endObject(); + } + endArray(); + + beginArray("rows"); + return true; +} + +bool JsonExport::exportQueryResultsRow(SqlResultsRowPtr row) +{ + beginArray(); + for (const QVariant& value : row->valueList()) + writeValue(value); + + endArray(); + return true; +} + +bool JsonExport::afterExportQueryResults() +{ + endArray(); + endObject(); + return true; +} + +bool JsonExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, const QHash providedData) +{ + UNUSED(providedData); + UNUSED(columnNames); + + beginObject(); + writeValue("type", "table"); + writeValue("database", database); + writeValue("name", table); + writeValue("withoutRowId", createTable->withOutRowId.isNull()); + writeValue("ddl", ddl); + + beginArray("columns"); + for (SqliteCreateTable::Column* col : createTable->columns) + { + beginObject(); + writeValue("name", col->name); + writeValue("type", col->type ? col->type->toDataType().toFullTypeString() : ""); + if (col->constraints.size() > 0) + { + beginArray("constraints"); + for (SqliteCreateTable::Column::Constraint* constr : col->constraints) + { + beginObject(); + writeValue("type", constr->typeString()); + writeValue("definition", constr->detokenize()); + endObject(); + } + endArray(); + } + endObject(); + } + endArray(); + + if (createTable->constraints.size() > 0) + { + beginArray("constraints"); + for (SqliteCreateTable::Constraint* constr : createTable->constraints) + { + beginObject(); + writeValue("type", constr->typeString()); + writeValue("definition", constr->detokenize()); + endObject(); + } + endArray(); + } + + beginArray("rows"); + return true; +} + +bool JsonExport::exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, const QHash providedData) +{ + UNUSED(providedData); + + beginObject(); + writeValue("type", "table"); + writeValue("database", database); + writeValue("name", table); + writeValue("virtual", true); + writeValue("module", createTable->module); + writeValue("ddl", ddl); + + beginArray("columns"); + for (const QString& col : columnNames) + writeValue(col); + + endArray(); + + if (createTable->args.size() > 0) + { + beginArray("moduleArgs"); + for (const QString& arg : createTable->args) + writeValue(arg); + + endArray(); + } + + beginArray("rows"); + return true; +} + +bool JsonExport::exportTableRow(SqlResultsRowPtr data) +{ + return exportQueryResultsRow(data); +} + +bool JsonExport::afterExportTable() +{ + endArray(); + endObject(); + return true; +} + +bool JsonExport::beforeExportDatabase(const QString& database) +{ + beginObject(); + writeValue("type", "database"); + writeValue("name", database); + beginArray("objects"); + return true; +} + +bool JsonExport::exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) +{ + beginObject(); + writeValue("type", "index"); + writeValue("database", database); + writeValue("name", name); + writeValue("unique", createIndex->uniqueKw); + + if (createIndex->where) + writeValue("partial", createIndex->where->detokenize()); + + writeValue("ddl", ddl); + endObject(); + return true; +} + +bool JsonExport::exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) +{ + beginObject(); + writeValue("type", "trigger"); + writeValue("database", database); + writeValue("name", name); + writeValue("ddl", ddl); + + QString timing = SqliteCreateTrigger::time(createTrigger->eventTime); + writeValue("timing", timing); + + QString event = createTrigger->event ? SqliteCreateTrigger::Event::typeToString(createTrigger->event->type) : ""; + writeValue("action", event); + + QString obj; + if (createTrigger->eventTime == SqliteCreateTrigger::Time::INSTEAD_OF) + obj = "view"; + else + obj = "table"; + + writeValue(obj, createTrigger->table); + + if (createTrigger->precondition) + writeValue("precondition", createTrigger->precondition->detokenize()); + + QStringList queryStrings; + for (SqliteQuery* q : createTrigger->queries) + queryStrings << q->detokenize(); + + writeValue("code", queryStrings.join("\n")); + endObject(); + return true; +} + +bool JsonExport::exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr createView) +{ + beginObject(); + writeValue("type", "view"); + writeValue("database", database); + writeValue("name", name); + writeValue("ddl", ddl); + writeValue("select", createView->select->detokenize()); + endObject(); + return true; +} + +bool JsonExport::afterExportDatabase() +{ + endArray(); + endObject(); + return true; +} + +bool JsonExport::beforeExport() +{ + setupConfig(); + return true; +} + +bool JsonExport::init() +{ + Q_INIT_RESOURCE(jsonexport); + return GenericExportPlugin::init(); +} + +void JsonExport::deinit() +{ + Q_CLEANUP_RESOURCE(jsonexport); +} + +void JsonExport::setupConfig() +{ + elementCounter.clear(); + elementCounter.push(0); + indent = (cfg.JsonExport.Format.get() == "format"); + indentDepth = 0; + updateIndent(); +} + +void JsonExport::incrIndent() +{ + elementCounter.push(0); + if (!indent) + return; + + indentDepth++; + updateIndent(); +} + +void JsonExport::decrIndent() +{ + elementCounter.pop(); + if (!indent) + return; + + indentDepth--; + updateIndent(); +} + +void JsonExport::updateIndent() +{ + static const QString singleIndent = QStringLiteral(" "); + indentStr = singleIndent.repeated(indentDepth); +} + +void JsonExport::incrElementCount() +{ + elementCounter.top()++; +} + +void JsonExport::write(const QString& str) +{ + GenericExportPlugin::write(indentStr + str); +} + +QString JsonExport::escapeString(const QString& str) +{ + QString copy = str; + return "\"" + + copy.replace("\"", "\\\"") + .replace("\\", "\\\\") + .replace("/", "\\/") + .replace("\b", "\\b") + .replace("\f", "\\f") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + + "\""; +} + +QString JsonExport::formatValue(const QVariant& val) +{ + if (val.isNull()) + return "null"; + + switch (val.type()) + { + case QVariant::Int: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + case QVariant::Double: + case QVariant::Bool: + return val.toString(); + default: + break; + } + + return escapeString(val.toString()); +} + +void JsonExport::beginObject() +{ + static const QString formatted = QStringLiteral("{\n"); + static const QString compact = QStringLiteral("{"); + + writePrefixBeforeNextElement(); + write(indent ? formatted : compact); + incrIndent(); +} + +void JsonExport::beginObject(const QString& key) +{ + static const QString formatted = QStringLiteral("%1: {\n"); + static const QString compact = QStringLiteral("%1:{"); + + QString escaped = escapeString(key); + + writePrefixBeforeNextElement(); + write(indent ? formatted.arg(escaped) : compact.arg(escaped)); + incrIndent(); +} + +void JsonExport::endObject() +{ + writePrefixBeforeEnd(); + decrIndent(); + write("}"); + incrElementCount(); +} + +void JsonExport::beginArray() +{ + static const QString formatted = QStringLiteral("[\n"); + static const QString compact = QStringLiteral("["); + + writePrefixBeforeNextElement(); + write(indent ? formatted : compact); + incrIndent(); +} + +void JsonExport::beginArray(const QString& key) +{ + static const QString formatted = QStringLiteral("%1: [\n"); + static const QString compact = QStringLiteral("%1:["); + + QString escaped = escapeString(key); + + writePrefixBeforeNextElement(); + write(indent ? formatted.arg(escaped) : compact.arg(escaped)); + incrIndent(); +} + +void JsonExport::endArray() +{ + writePrefixBeforeEnd(); + decrIndent(); + write("]"); + incrElementCount(); +} + +void JsonExport::writeValue(const QVariant& value) +{ + writePrefixBeforeNextElement(); + write(formatValue(value)); + incrElementCount(); +} + +void JsonExport::writeValue(const QString& key, const QVariant& value) +{ + static const QString formatted = QStringLiteral("%1: %2"); + static const QString compact = QStringLiteral("%1:%2"); + + QString escaped = escapeString(key); + QString val = formatValue(value); + + writePrefixBeforeNextElement(); + write(indent ? formatted.arg(escaped, val) : compact.arg(escaped, val)); + incrElementCount(); +} + +void JsonExport::writePrefixBeforeEnd() +{ + if (indent && elementCounter.top() > 0) + GenericExportPlugin::write("\n"); +} + +void JsonExport::writePrefixBeforeNextElement() +{ + if (elementCounter.top() > 0) + { + GenericExportPlugin::write(","); + if (indent) + GenericExportPlugin::write("\n"); + } +} diff --git a/Plugins/JsonExport/jsonexport.h b/Plugins/JsonExport/jsonexport.h new file mode 100644 index 0000000..150592d --- /dev/null +++ b/Plugins/JsonExport/jsonexport.h @@ -0,0 +1,78 @@ +#ifndef JSONEXPORT_H +#define JSONEXPORT_H + +#include "jsonexport_global.h" +#include "plugins/genericexportplugin.h" +#include "config_builder.h" +#include + +CFG_CATEGORIES(JsonExportConfig, + CFG_CATEGORY(JsonExport, + CFG_ENTRY(QString, Format, "format") + ) +) + +class JSONEXPORTSHARED_EXPORT JsonExport : public GenericExportPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("jsonexport.json") + + public: + JsonExport(); + + QString getFormatName() const; + ExportManager::StandardConfigFlags standardOptionsToEnable() const; + QString getExportConfigFormName() const; + CfgMain* getConfig(); + void validateOptions(); + QString defaultFileExtension() const; + QString getDefaultEncoding() const; + bool beforeExportQueryResults(const QString& query, QList& columns, + const QHash providedData); + bool exportQueryResultsRow(SqlResultsRowPtr row); + bool afterExportQueryResults(); + bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash providedData); + bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, + const QHash providedData); + bool exportTableRow(SqlResultsRowPtr data); + bool afterExportTable(); + bool beforeExportDatabase(const QString& database); + bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex); + bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger); + bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr createView); + bool afterExportDatabase(); + bool beforeExport(); + bool init(); + void deinit(); + + private: + void setupConfig(); + void incrIndent(); + void decrIndent(); + void updateIndent(); + void incrElementCount(); + void write(const QString& str); + QString escapeString(const QString& str); + QString formatValue(const QVariant& val); + void beginObject(); + void beginObject(const QString& key); + void endObject(); + void beginArray(); + void beginArray(const QString& key); + void endArray(); + void writeValue(const QVariant& value); + void writeValue(const QString& key, const QVariant& value); + void writePrefixBeforeEnd(); + void writePrefixBeforeNextElement(); + + CFG_LOCAL(JsonExportConfig, cfg) + QStack elementCounter; + bool indent = false; + int indentDepth = 0; + QString indentStr; + QString newLineStr; + QString codecName; +}; + +#endif // JSONEXPORT_H diff --git a/Plugins/JsonExport/jsonexport.json b/Plugins/JsonExport/jsonexport.json new file mode 100644 index 0000000..c56270c --- /dev/null +++ b/Plugins/JsonExport/jsonexport.json @@ -0,0 +1,7 @@ +{ + "type": "ExportPlugin", + "title": "JSON export", + "description": "Provides JSON format for exporting.", + "version": 10000, + "author": "SalSoft" +} diff --git a/Plugins/JsonExport/jsonexport.qrc b/Plugins/JsonExport/jsonexport.qrc new file mode 100644 index 0000000..19ea921 --- /dev/null +++ b/Plugins/JsonExport/jsonexport.qrc @@ -0,0 +1,5 @@ + + + jsonexport.ui + + diff --git a/Plugins/JsonExport/jsonexport.ui b/Plugins/JsonExport/jsonexport.ui new file mode 100644 index 0000000..ebb0d78 --- /dev/null +++ b/Plugins/JsonExport/jsonexport.ui @@ -0,0 +1,66 @@ + + + JsonExportConfig + + + + 0 + 0 + 345 + 100 + + + + Form + + + + + + Output format + + + + + + Format document (new lines, indentation) + + + true + + + format + + + JsonExport.Format + + + + + + + Compress (everything in one line) + + + compress + + + JsonExport.Format + + + + + + + + + + + ConfigRadioButton + QRadioButton +
common/configradiobutton.h
+
+
+ + +
diff --git a/Plugins/JsonExport/jsonexport_global.h b/Plugins/JsonExport/jsonexport_global.h new file mode 100644 index 0000000..ce86078 --- /dev/null +++ b/Plugins/JsonExport/jsonexport_global.h @@ -0,0 +1,12 @@ +#ifndef JSONEXPORT_GLOBAL_H +#define JSONEXPORT_GLOBAL_H + +#include + +#if defined(JSONEXPORT_LIBRARY) +# define JSONEXPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define JSONEXPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // JSONEXPORT_GLOBAL_H diff --git a/Plugins/PdfExport/PdfExport.pro b/Plugins/PdfExport/PdfExport.pro new file mode 100644 index 0000000..46dda37 --- /dev/null +++ b/Plugins/PdfExport/PdfExport.pro @@ -0,0 +1,26 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-06-23T01:06:41 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +TARGET = PdfExport +TEMPLATE = lib + +DEFINES += PDFEXPORT_LIBRARY + +SOURCES += pdfexport.cpp + +HEADERS += pdfexport.h\ + pdfexport_global.h + +OTHER_FILES += \ + pdfexport.json + +FORMS += \ + pdfexport.ui + +RESOURCES += \ + pdfexport.qrc diff --git a/Plugins/PdfExport/pdfexport.cpp b/Plugins/PdfExport/pdfexport.cpp new file mode 100644 index 0000000..da15ab7 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.cpp @@ -0,0 +1,1420 @@ +#include "pdfexport.h" +#include "common/unused.h" +#include "uiutils.h" +#include "log.h" +#include +#include +#include +#include + +QString PdfExport::bulletChar = "\u2022"; + +bool PdfExport::init() +{ + textOption = new QTextOption(); + textOption->setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + return GenericExportPlugin::init(); +} + +void PdfExport::deinit() +{ + safe_delete(textOption); +} + +QPagedPaintDevice* PdfExport::createPaintDevice(const QString& documentTitle) +{ + QPdfWriter* pdfWriter = new QPdfWriter(output); + pdfWriter->setTitle(documentTitle); + pdfWriter->setCreator(tr("SQLiteStudio v%1").arg(SQLITESTUDIO->getVersionString())); + return pdfWriter; +} + +QString PdfExport::getFormatName() const +{ + return "PDF"; +} + +ExportManager::StandardConfigFlags PdfExport::standardOptionsToEnable() const +{ + return 0; +} + +ExportManager::ExportProviderFlags PdfExport::getProviderFlags() const +{ + return ExportManager::DATA_LENGTHS|ExportManager::ROW_COUNT; +} + +void PdfExport::validateOptions() +{ +} + +QString PdfExport::defaultFileExtension() const +{ + return "pdf"; +} + +bool PdfExport::beforeExportQueryResults(const QString& query, QList& columns, const QHash providedData) +{ + UNUSED(query); + + if (!beginDoc(tr("SQL query results"))) + return false; + + totalRows = providedData[ExportManager::ROW_COUNT].toInt(); + + QStringList columnNames; + for (const QueryExecutor::ResultColumnPtr& col : columns) + columnNames << col->displayName; + + clearDataHeaders(); + exportDataColumnsHeader(columnNames); + + QList columnDataLengths = getColumnDataLengths(columnNames.size(), providedData); + calculateDataColumnWidths(columnNames, columnDataLengths); + return true; +} + +bool PdfExport::exportQueryResultsRow(SqlResultsRowPtr row) +{ + exportDataRow(row->valueList()); + return true; +} + +bool PdfExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, const QHash providedData) +{ + UNUSED(columnNames); + UNUSED(database); + UNUSED(ddl); + + if (isTableExport() && !beginDoc(tr("Exported table: %1").arg(table))) + return false; + + exportObjectHeader(tr("Table: %1").arg(table)); + + QStringList tableDdlColumns = {tr("Column"), tr("Data type"), tr("Constraints")}; + exportObjectColumnsHeader(tableDdlColumns); + + QString colDef; + QString colType; + QStringList columnsAndTypes; + int colNamesLength = 0; + int dataTypeLength = 0; + for (SqliteCreateTable::Column* col : createTable->columns) + { + colDef = col->name; + colNamesLength = qMax(colNamesLength, colDef.size()); + colType = ""; + if (col->type) + { + colType = col->type->toDataType().toFullTypeString(); + colDef += "\n" + colType; + dataTypeLength = qMax(dataTypeLength, colType.size()); + } + + columnsAndTypes << colDef; + } + + QList columnDataLengths = {colNamesLength, dataTypeLength, 0}; + calculateDataColumnWidths(tableDdlColumns, columnDataLengths, 2); + + for (SqliteCreateTable::Column* col : createTable->columns) + exportTableColumnRow(col); + + if (createTable->constraints.size() > 0) + { + QStringList tableDdlColumns = {tr("Global table constraints")}; + exportObjectColumnsHeader(tableDdlColumns); + exportTableConstraintsRow(createTable->constraints); + } + + flushObjectPages(); + + prepareTableDataExport(table, columnsAndTypes, providedData); + return true; +} + +bool PdfExport::exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, const QHash providedData) +{ + UNUSED(columnNames); + UNUSED(database); + UNUSED(ddl); + UNUSED(createTable); + + if (isTableExport() && !beginDoc(tr("Exported table: %1").arg(table))) + return false; + + prepareTableDataExport(table, columnNames, providedData); + return true; +} + +void PdfExport::prepareTableDataExport(const QString& table, const QStringList& columnNames, const QHash providedData) +{ + resetDataTable(); + totalRows = providedData[ExportManager::ROW_COUNT].toInt(); + + // Prepare for exporting data row + clearDataHeaders(); + if (!isTableExport()) // for database export we need to mark what is this object name + exportDataHeader(tr("Table: %1").arg(table)); + + exportDataColumnsHeader(columnNames); + + QList columnDataLengths = getColumnDataLengths(columnNames.size(), providedData); + calculateDataColumnWidths(columnNames, columnDataLengths); +} + +QList PdfExport::getColumnDataLengths(int columnCount, const QHash providedData) +{ + QList columnDataLengths = providedData[ExportManager::DATA_LENGTHS].value>(); + if (columnDataLengths.size() < columnCount) + { + qWarning() << "PdfExport: column widths provided by ExportWorker (" << columnDataLengths.size() + << ") is less than number of columns to export (" << columnCount << ")."; + } + + // Fill up column data widths if there are any missing from the provided data (should not happen) + while (columnDataLengths.size() < columnCount) + columnDataLengths << maxColWidth; + + for (int& val : columnDataLengths) + { + if (val > cellDataLimit) + val = cellDataLimit; + } + + return columnDataLengths; +} + +bool PdfExport::exportTableRow(SqlResultsRowPtr data) +{ + exportDataRow(data->valueList()); + return true; +} + +bool PdfExport::afterExport() +{ + endDoc(); + return true; +} + +bool PdfExport::afterExportTable() +{ + flushDataPages(true); + return true; +} + +bool PdfExport::afterExportQueryResults() +{ + flushDataPages(true); + return true; +} + +bool PdfExport::beforeExportDatabase(const QString& database) +{ + return beginDoc(tr("Exported database: %1").arg(database)); +} + +bool PdfExport::exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) +{ + UNUSED(database); + UNUSED(ddl); + + exportObjectHeader(tr("Index: %1").arg(name)); + + QStringList indexColumns = {tr("Property", "index header"), tr("Value", "index header")}; + exportObjectColumnsHeader(indexColumns); + + exportObjectRow({tr("Indexed table"), name}); + exportObjectRow({tr("Unique index"), (createIndex->uniqueKw ? tr("Yes") : tr("No"))}); + + indexColumns = {tr("Column"), tr("Collation"), tr("Sort order")}; + exportObjectColumnsHeader(indexColumns); + + QString sort; + for (SqliteIndexedColumn* idxCol : createIndex->indexedColumns) + { + if (idxCol->sortOrder != SqliteSortOrder::null) + sort = sqliteSortOrder(idxCol->sortOrder); + else + sort = ""; + + exportObjectRow({idxCol->name, idxCol->collate, sort}); + } + + if (createIndex->where) + { + indexColumns = {tr("Partial index condition")}; + exportObjectColumnsHeader(indexColumns); + exportObjectRow(createIndex->where->detokenize()); + } + + flushObjectPages(); + return true; +} + +bool PdfExport::exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) +{ + UNUSED(database); + UNUSED(ddl); + + exportObjectHeader(tr("Trigger: %1").arg(name)); + + QStringList trigColumns = {tr("Property", "trigger header"), tr("Value", "trigger header")}; + exportObjectColumnsHeader(trigColumns); + exportObjectRow({tr("Activation time"), SqliteCreateTrigger::time(createTrigger->eventTime)}); + + QString event = createTrigger->event ? SqliteCreateTrigger::Event::typeToString(createTrigger->event->type) : ""; + exportObjectRow({tr("For action"), event}); + + QString onObj; + if (createTrigger->eventTime == SqliteCreateTrigger::Time::INSTEAD_OF) + onObj = tr("On view"); + else + onObj = tr("On table"); + + exportObjectRow({onObj, createTrigger->table}); + + QString cond = createTrigger->precondition ? createTrigger->precondition->detokenize() : ""; + exportObjectRow({tr("Activation condition"), cond}); + + QStringList queryStrings; + for (SqliteQuery* q : createTrigger->queries) + queryStrings << q->detokenize(); + + exportObjectColumnsHeader({tr("Code executed")}); + exportObjectRow(queryStrings.join("\n")); + + flushObjectPages(); + return true; +} + +bool PdfExport::exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view) +{ + UNUSED(database); + UNUSED(ddl); + + exportObjectHeader(tr("View: %1").arg(name)); + exportObjectColumnsHeader({tr("Query:")}); + exportObjectRow(view->select->detokenize()); + + flushObjectPages(); + return true; +} + +bool PdfExport::isBinaryData() const +{ + return true; +} + +bool PdfExport::beginDoc(const QString& title) +{ + safe_delete(painter); + safe_delete(pagedWriter); + pagedWriter = createPaintDevice(title); + if (!pagedWriter) + return false; + + painter = new QPainter(pagedWriter); + painter->setBrush(Qt::NoBrush); + painter->setPen(QPen(Qt::black, lineWidth)); + + setupConfig(); + return true; +} + +void PdfExport::endDoc() +{ + drawFooter(); +} + +void PdfExport::cleanupAfterExport() +{ + safe_delete(painter); + safe_delete(pagedWriter); +} + +void PdfExport::setupConfig() +{ + pagedWriter->setPageSize(convertPageSize(cfg.PdfExport.PageSize.get())); + pageWidth = pagedWriter->width(); + pageHeight = pagedWriter->height(); + pointsPerMm = pageWidth / pagedWriter->pageSizeMM().width(); + + stdFont = cfg.PdfExport.Font.get(); + stdFont.setPointSize(cfg.PdfExport.FontSize.get()); + boldFont = stdFont; + boldFont.setBold(true); + italicFont = stdFont; + italicFont.setItalic(true); + painter->setFont(stdFont); + + topMargin = mmToPoints(cfg.PdfExport.TopMargin.get()); + rightMargin = mmToPoints(cfg.PdfExport.RightMargin.get()); + leftMargin = mmToPoints(cfg.PdfExport.LeftMargin.get()); + bottomMargin = mmToPoints(cfg.PdfExport.BottomMargin.get()); + updateMargins(); + + maxColWidth = pageWidth / 5; + padding = mmToPoints(cfg.PdfExport.Padding.get()); + + QRectF rect = painter->boundingRect(QRectF(padding, padding, pageWidth - 2 * padding, 1), "X", *textOption); + minRowHeight = rect.height() + padding * 2; + maxRowHeight = qMax((int)(pageHeight * 0.225), minRowHeight); + rowsToPrebuffer = (int)qCeil((double)pageHeight / minRowHeight); + + cellDataLimit = cfg.PdfExport.MaxCellBytes.get(); + printRowNum = cfg.PdfExport.PrintRowNum.get(); + printPageNumbers = cfg.PdfExport.PrintPageNumbers.get(); + + lastRowY = getContentsTop(); + currentPage = -1; + rowNum = 1; +} + +void PdfExport::updateMargins() +{ + pageWidth -= (leftMargin + rightMargin); + pageHeight -= (topMargin + bottomMargin); + painter->setClipRect(QRect(leftMargin, topMargin, pageWidth, pageHeight)); + + if (printPageNumbers) + { + int pageNumHeight = getPageNumberHeight(); + bottomMargin += pageNumHeight; + pageHeight -= pageNumHeight; + } + + // In order to render full width of the line, we need to add more margin, a half of the line width + leftMargin += lineWidth / 2; + rightMargin += lineWidth / 2; + topMargin += lineWidth / 2; + bottomMargin += lineWidth / 2; + pageWidth -= lineWidth; + pageHeight -= lineWidth; +} + +void PdfExport::clearDataHeaders() +{ + headerRow.reset(); + columnsHeaderRow.reset(); +} + +void PdfExport::resetDataTable() +{ + clearDataHeaders(); + bufferedDataRows.clear(); + rowNum = 0; +} + +void PdfExport::exportDataRow(const QList& data) +{ + DataCell cell; + DataRow row; + + for (const QVariant& value : data) + { + switch (value.type()) + { + case QVariant::Int: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + case QVariant::Double: + cell.alignment = Qt::AlignRight; + break; + default: + cell.alignment = Qt::AlignLeft; + break; + } + + if (value.isNull()) + { + cell.alignment = Qt::AlignCenter; + cell.isNull = true; + cell.contents = QStringLiteral("NULL"); + } + else + { + cell.isNull = false; + cell.contents = value.toString(); + } + row.cells << cell; + } + + bufferedDataRows << row; + checkForDataRender(); +} + +void PdfExport::exportObjectHeader(const QString& contents) +{ + ObjectRow row; + ObjectCell cell; + cell.headerBackground = true; + cell.contents << contents; + cell.bold = true; + cell.alignment = Qt::AlignCenter; + row.cells << cell; + + row.type = ObjectRow::Type::SINGLE; + row.recalculateColumnWidths = true; + bufferedObjectRows << row; +} + +void PdfExport::exportObjectColumnsHeader(const QStringList& columns) +{ + ObjectRow row; + ObjectCell cell; + + for (const QString& col : columns) + { + cell.headerBackground = true; + cell.contents.clear(); + cell.contents << col; + cell.alignment = Qt::AlignCenter; + row.cells << cell; + } + + row.recalculateColumnWidths = true; + row.type = ObjectRow::Type::MULTI; + bufferedObjectRows << row; +} + +void PdfExport::exportTableColumnRow(SqliteCreateTable::Column* column) +{ + ObjectRow row; + row.type = ObjectRow::Type::MULTI; + + ObjectCell cell; + cell.contents << column->name; + row.cells << cell; + cell.contents.clear(); + + if (column->type) + cell.contents << column->type->toDataType().toFullTypeString(); + else + cell.contents << ""; + + row.cells << cell; + cell.contents.clear(); + + if (column->constraints.size() > 0) + { + for (SqliteCreateTable::Column::Constraint* constr : column->constraints) + cell.contents << constr->detokenize(); + + cell.type = ObjectCell::Type::LIST; + } + else + { + cell.contents << ""; + } + row.cells << cell; + cell.contents.clear(); + + bufferedObjectRows << row; +} + +void PdfExport::exportTableConstraintsRow(const QList& constrList) +{ + ObjectRow row; + row.type = ObjectRow::Type::SINGLE; + + ObjectCell cell; + cell.type = ObjectCell::Type::LIST; + + if (constrList.size() > 0) + { + for (SqliteCreateTable::Constraint* constr : constrList) + cell.contents << constr->detokenize(); + } + else + { + cell.contents << ""; + } + row.cells << cell; + + bufferedObjectRows << row; +} + +void PdfExport::exportObjectRow(const QStringList& values) +{ + ObjectRow row; + row.type = ObjectRow::Type::MULTI; + + ObjectCell cell; + for (const QString& value : values) + { + cell.contents << value; + row.cells << cell; + cell.contents.clear(); + } + + bufferedObjectRows << row; +} + +void PdfExport::exportObjectRow(const QString& value) +{ + ObjectRow row; + row.type = ObjectRow::Type::SINGLE; + + ObjectCell cell; + cell.contents << value; + row.cells << cell; + + bufferedObjectRows << row; +} + +int PdfExport::calculateRowHeight(int maxTextWidth, const QStringList& listContents) +{ + int textWidth = maxTextWidth - calculateBulletPrefixWidth(); + int totalHeight = 0; + for (const QString& contents : listContents) + totalHeight += calculateRowHeight(textWidth, contents); + + return totalHeight; +} + +int PdfExport::calculateBulletPrefixWidth() +{ + static QString prefix = bulletChar + " "; + + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + return painter->boundingRect(QRect(0, 0, 1, 1), prefix, *textOption).width(); +} + +void PdfExport::checkForDataRender() +{ + if (bufferedDataRows.size() >= rowsToPrebuffer) + flushDataPages(); +} + +void PdfExport::flushObjectPages() +{ + if (bufferedObjectRows.isEmpty()) + return; + + int y = getContentsTop(); + int totalHeight = lastRowY - y; + + if (totalHeight > 0) + { + totalHeight += minRowHeight * 2; // a space between objects on one page + y += totalHeight; + } + else + newPage(); + + while (!bufferedObjectRows.isEmpty()) + { + ObjectRow& row = bufferedObjectRows.first(); + + if (row.recalculateColumnWidths || row.cells.size() != calculatedObjectColumnWidths.size()) + calculateObjectColumnWidths(); + + totalHeight += row.height; + if (totalHeight > pageHeight) + { + newPage(); + y = getContentsTop(); + totalHeight = row.height; + } + flushObjectRow(row, y); + + y += row.height; + + bufferedObjectRows.removeFirst(); + } + + lastRowY = getContentsTop() + totalHeight; +} + +void PdfExport::drawObjectTopLine(int y) +{ + painter->drawLine(getContentsLeft(), y, getContentsRight(), y); +} + +void PdfExport::drawObjectCellHeaderBackground(int x1, int y1, int x2, int y2) +{ + painter->save(); + painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern)); + painter->setPen(Qt::NoPen); + painter->drawRect(x1, y1, x2 - x1, y2 - y1); + painter->restore(); +} + +void PdfExport::drawFooter() +{ + QString footer = tr("Document generated with SQLiteStudio v%1").arg(SQLITESTUDIO->getVersionString()); + + QTextOption opt = *textOption; + opt.setAlignment(Qt::AlignRight); + + int y = lastRowY + minRowHeight; + int height = pageHeight - y; + int txtHeight = painter->boundingRect(QRect(0, 0, pageWidth, height), footer, opt).height(); + if ((y + txtHeight) > pageHeight) + { + newPage(); + y = getContentsTop(); + } + + painter->save(); + painter->setFont(italicFont); + painter->drawText(QRect(getContentsLeft(), y, pageWidth, txtHeight), footer, opt); + painter->restore(); +} + +void PdfExport::flushObjectRow(const PdfExport::ObjectRow& row, int y) +{ + painter->save(); + int x = getContentsLeft(); + int bottom = y + row.height; + int top = y; + int left = getContentsLeft(); + int right = getContentsRight(); + switch (row.type) + { + case ObjectRow::Type::SINGLE: + { + const ObjectCell& cell = row.cells.first(); + if (cell.headerBackground) + drawObjectCellHeaderBackground(left, y, right, bottom); + + painter->drawLine(left, y, left, bottom); + painter->drawLine(right, y, right, bottom); + painter->drawLine(left, top, right, top); + painter->drawLine(left, bottom, right, bottom); + + flushObjectCell(cell, left, y, pageWidth, row.height); + break; + } + case ObjectRow::Type::MULTI: + { + int width = 0; + for (int col = 0, total = calculatedObjectColumnWidths.size(); col < total; ++col) + { + width = calculatedObjectColumnWidths[col]; + if (row.cells[col].headerBackground) + drawObjectCellHeaderBackground(x, y, x + width, bottom); + + x += width; + } + + x = left; + painter->drawLine(x, y, x, bottom); + for (int w : calculatedObjectColumnWidths) + { + x += w; + painter->drawLine(x, y, x, bottom); + } + painter->drawLine(left, top, right, top); + painter->drawLine(left, bottom, right, bottom); + + x = left; + for (int col = 0, total = calculatedObjectColumnWidths.size(); col < total; ++col) + { + const ObjectCell& cell = row.cells[col]; + width = calculatedObjectColumnWidths[col]; + flushObjectCell(cell, x, y, width, row.height); + x += width; + } + break; + } + } + painter->restore(); +} + +void PdfExport::flushObjectCell(const PdfExport::ObjectCell& cell, int x, int y, int w, int h) +{ + QTextOption opt = *textOption; + opt.setAlignment(cell.alignment); + + if (cell.bold) + painter->setFont(boldFont); + else if (cell.italic) + painter->setFont(italicFont); + + switch (cell.type) + { + case ObjectCell::Type::NORMAL: + { + painter->drawText(QRect(x + padding, y + padding, w - 2 * padding, h - 2 * padding), cell.contents.first(), opt); + break; + } + case ObjectCell::Type::LIST: + { + static QString prefix = bulletChar + " "; + int prefixWidth = calculateBulletPrefixWidth(); + x += padding; + y += padding; + w -= 2 * padding; + h -= 2 * padding; + int txtX = x + prefixWidth; + int txtW = w - prefixWidth; + + QTextOption prefixOpt = opt; + prefixOpt.setAlignment(opt.alignment() | Qt::AlignTop); + + int txtH = 0; + for (const QString& contents : cell.contents) + { + txtH = calculateRowHeight(txtW, contents); + painter->drawText(QRect(x, y, prefixWidth, txtH), prefix, prefixOpt); + painter->drawText(QRect(txtX, y, txtW, txtH), contents, opt); + y += txtH; + } + break; + } + } +} + +void PdfExport::calculateObjectColumnWidths(int columnToExpand) +{ + calculatedObjectColumnWidths.clear(); + if (bufferedObjectRows.size() == 0) + return; + + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + int colCount = bufferedObjectRows.first().cells.size(); + for (int i = 0; i < colCount; i++) + calculatedObjectColumnWidths << 0; + + int width = 0; + for (const ObjectRow& row : bufferedObjectRows) + { + if (row.cells.size() != colCount) + break; + + for (int col = 0; col < colCount; col++) + { + width = painter->boundingRect(QRectF(0, 0, 1, 1), row.cells[col].contents.join("\n"), opt).width(); + width += 2 * padding; + calculatedObjectColumnWidths[col] = qMax(calculatedObjectColumnWidths[col], width); + } + } + + int totalWidth = correctMaxObjectColumnWidths(colCount, columnToExpand); + if (totalWidth < pageWidth) + { + int col = (columnToExpand > -1) ? columnToExpand : (colCount - 1); + calculatedObjectColumnWidths[col] += (pageWidth - totalWidth); + } + + calculateObjectRowHeights(); +} + +int PdfExport::correctMaxObjectColumnWidths(int colCount, int columnToExpand) +{ + int totalWidth = 0; + for (int w : calculatedObjectColumnWidths) + totalWidth += w; + + int maxWidth = pageWidth / colCount; + if (totalWidth <= pageWidth) + return totalWidth; + + int tmpWidth = 0; + for (int col = 0; col < colCount && totalWidth > pageWidth; col++) + { + if (calculatedObjectColumnWidths[col] <= maxWidth) + continue; + + if (col == columnToExpand) + continue; // will handle that column as last one (if needed) + + tmpWidth = calculatedObjectColumnWidths[col]; + if ((totalWidth - calculatedObjectColumnWidths[col] + maxWidth) <= pageWidth) + { + calculatedObjectColumnWidths[col] -= (pageWidth - totalWidth + calculatedObjectColumnWidths[col] - maxWidth); + return pageWidth; // the 'if' condition guarantees that shrinking this column that much will give us pageWidth + } + else + calculatedObjectColumnWidths[col] = maxWidth; + + totalWidth -= tmpWidth - calculatedObjectColumnWidths[col]; + } + + if (columnToExpand > -1 && totalWidth > pageWidth) + { + tmpWidth = calculatedObjectColumnWidths[columnToExpand]; + if ((totalWidth - calculatedObjectColumnWidths[columnToExpand] + maxWidth) <= pageWidth) + calculatedObjectColumnWidths[columnToExpand] -= (pageWidth - totalWidth + calculatedObjectColumnWidths[columnToExpand] - maxWidth); + else + calculatedObjectColumnWidths[columnToExpand] = maxWidth; + } + + return pageWidth; +} + +void PdfExport::calculateObjectRowHeights() +{ + int maxHeight = 0; + int colWidth = 0; + int height = 0; + int colCount = calculatedObjectColumnWidths.size(); + for (ObjectRow& row : bufferedObjectRows) + { + if (row.cells.size() != colCount) + return; // stop at this row, further calculation will be done when columns are recalculated + + maxHeight = 0; + for (int col = 0; col < colCount; ++col) + { + colWidth = calculatedObjectColumnWidths[col]; + const ObjectCell& cell = row.cells[col]; + + switch (cell.type) + { + case ObjectCell::Type::NORMAL: + height = calculateRowHeight(colWidth, cell.contents.first()); + break; + case ObjectCell::Type::LIST: + height = calculateRowHeight(colWidth, cell.contents); + break; + } + maxHeight = qMax(maxHeight, height); + } + + row.height = qMin(maxRowHeight, maxHeight); + } +} + +void PdfExport::flushDataPages(bool forceRender) +{ + calculateDataRowHeights(); + + int rowsToRender = 0; + int totalRowHeight = 0; + int colStartAt = 0; + while ((bufferedDataRows.size() >= rowsToPrebuffer) || (forceRender && bufferedDataRows.size() > 0)) + { + // Calculate how many rows we can render on single page + rowsToRender = 0; + totalRowHeight = totalHeaderRowsHeight; + for (const DataRow& row : bufferedDataRows) + { + rowsToRender++; + totalRowHeight += row.height; + if (totalRowHeight >= pageHeight) + { + rowsToRender--; + break; + } + } + + // Render limited number of columns and rows per single page + colStartAt = 0; + for (int cols : columnsPerPage) + { + newPage(); + flushDataRowsPage(colStartAt, colStartAt + cols, rowsToRender); + colStartAt += cols; + } + + for (int i = 0; i < rowsToRender; i++) + bufferedDataRows.removeFirst(); + + rowNum += rowsToRender; + } +} + +void PdfExport::flushDataRowsPage(int columnStart, int columnEndBefore, int rowsToRender) +{ + QList allRows; + if (headerRow) + allRows += *headerRow; + + if (columnsHeaderRow) + allRows += *columnsHeaderRow; + + allRows += bufferedDataRows.mid(0, rowsToRender); + + int left = getContentsLeft(); + int right = getContentsRight(); + int top = getContentsTop(); + + // Calculating width of all columns on this page + int totalColumnsWidth = sum(calculatedDataColumnWidths.mid(columnStart, columnEndBefore - columnStart)); + int totalColumnsWidthWithRowId = totalColumnsWidth + rowNumColumnWidth; + + // Calculating height of all rows + int totalRowsHeight = 0; + for (const DataRow& row : allRows) + totalRowsHeight += row.height; + + // Draw header background + int x = getDataColumnsStartX(); + painter->save(); + painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern)); + painter->setPen(Qt::NoPen); + painter->drawRect(QRect(x, top, totalColumnsWidth, totalHeaderRowsHeight)); + painter->restore(); + + // Draw rowNum background + if (printRowNum) + { + painter->save(); + painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern)); + painter->setPen(Qt::NoPen); + painter->drawRect(QRect(left, top, rowNumColumnWidth, totalRowsHeight)); + painter->restore(); + } + + // Draw horizontal lines + int y = top; + int horizontalLineEnd = x + totalColumnsWidth; + painter->drawLine(left, y, horizontalLineEnd, y); + for (const DataRow& row : allRows) + { + y += row.height; + painter->drawLine(left, y, horizontalLineEnd, y); + } + + // Draw dashed horizontal lines if there are more columns on the next page and there is space on the right side + if (columnEndBefore < calculatedDataColumnWidths.size() && horizontalLineEnd < right) + { + y = top; + painter->save(); + QPen pen(Qt::lightGray, lineWidth, Qt::DashLine); + pen.setDashPattern(QVector({5.0, 3.0})); + painter->setPen(pen); + painter->drawLine(horizontalLineEnd, y, right, y); + for (const DataRow& row : allRows) + { + y += row.height; + painter->drawLine(horizontalLineEnd, y, right, y); + } + painter->restore(); + } + + // Finding first row to start vertical lines from. It's either a COLUMNS_HEADER, or first data row, after headers. + int verticalLinesStart = top; + if (headerRow) + verticalLinesStart += headerRow->height; + + // Draw vertical lines + x = getDataColumnsStartX(); + painter->drawLine(left, top, left, top + totalRowsHeight); + if (printRowNum) + painter->drawLine(x, verticalLinesStart, x, top + totalRowsHeight); + + for (int col = columnStart; col < columnEndBefore; col++) + { + x += calculatedDataColumnWidths[col]; + painter->drawLine(x, (col+1 == columnEndBefore) ? top : verticalLinesStart, x, top + totalRowsHeight); + } + + // Draw header rows + y = top; + if (headerRow) + flushDataHeaderRow(*headerRow, y, totalColumnsWidthWithRowId, columnStart, columnEndBefore); + + if (columnsHeaderRow) + flushDataHeaderRow(*columnsHeaderRow, y, totalColumnsWidthWithRowId, columnStart, columnEndBefore); + + // Draw data + int localRowNum = rowNum; + for (int rowCounter = 0; rowCounter < rowsToRender && !bufferedDataRows.isEmpty(); rowCounter++) + flushDataRow(bufferedDataRows[rowCounter], y, columnStart, columnEndBefore, localRowNum++); + + lastRowY = y; +} + +void PdfExport::flushDataRow(const DataRow& row, int& y, int columnStart, int columnEndBefore, int localRowNum) +{ + int textWidth = 0; + int textHeight = 0; + int colWidth = 0; + int x = getContentsLeft(); + + y += padding; + if (printRowNum) + { + QTextOption opt = *textOption; + opt.setAlignment(Qt::AlignRight); + + x += padding; + textWidth = rowNumColumnWidth - padding * 2; + textHeight = row.height - padding * 2; + flushDataCell(QRect(x, y, textWidth, textHeight), QString::number(localRowNum), &opt); + x += rowNumColumnWidth - padding; + } + + for (int col = columnStart; col < columnEndBefore; col++) + { + const DataCell& cell = row.cells[col]; + colWidth = calculatedDataColumnWidths[col]; + + x += padding; + textWidth = colWidth - padding * 2; + textHeight = row.height - padding * 2; + flushDataCell(QRect(x, y, textWidth, textHeight), cell); + x += colWidth - padding; + } + y += row.height - padding; +} + +void PdfExport::flushDataCell(const QRect& rect, const PdfExport::DataCell& cell) +{ + QTextOption opt = *textOption; + opt.setAlignment(cell.alignment); + + painter->save(); + if (cell.isNull) + { + painter->setPen(cfg.PdfExport.NullValueColor.get()); + painter->setFont(italicFont); + } + + painter->drawText(rect, cell.contents.left(cellDataLimit), opt); + painter->restore(); +} + +void PdfExport::flushDataCell(const QRect& rect, const QString& contents, QTextOption* opt) +{ + painter->drawText(rect, contents.left(cellDataLimit), *opt); +} + +void PdfExport::flushDataHeaderRow(const PdfExport::DataRow& row, int& y, int totalColsWidth, int columnStart, int columnEndBefore) +{ + QTextOption opt = *textOption; + opt.setAlignment(Qt::AlignHCenter); + int x = getContentsLeft(); + y += padding; + switch (row.type) + { + case DataRow::Type::TOP_HEADER: + { + x += padding; + painter->save(); + painter->setFont(boldFont); + painter->drawText(QRect(x, y, totalColsWidth - 2 * padding, row.height - 2 * padding), row.cells.first().contents, opt); + painter->restore(); + break; + } + case DataRow::Type::COLUMNS_HEADER: + { + if (printRowNum) + { + x += padding; + int textWidth = rowNumColumnWidth - padding * 2; + int textHeight = row.height - padding * 2; + painter->drawText(QRect(x, y, textWidth, textHeight), "#", opt); + x += rowNumColumnWidth - padding; + } + + for (int col = columnStart; col < columnEndBefore; col++) + flushDataHeaderCell(x, y, row, col, &opt); + + break; + } + case DataRow::Type::NORMAL: + break; // no-op + } + y += row.height - padding; +} + +void PdfExport::flushDataHeaderCell(int& x, int y, const PdfExport::DataRow& row, int col, QTextOption* opt) +{ + x += padding; + painter->drawText(QRect(x, y, calculatedDataColumnWidths[col] - 2 * padding, row.height - 2 * padding), row.cells[col].contents, *opt); + x += calculatedDataColumnWidths[col] - padding; +} + +void PdfExport::renderPageNumber() +{ + if (!printPageNumbers) + return; + + QString page = QString::number(currentPage + 1); + + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + painter->save(); + painter->setFont(italicFont); + QRect rect = painter->boundingRect(QRect(0, 0, 1, 1), page, opt).toRect(); + int x = getContentsRight() - rect.width(); + int y = getContentsBottom(); // the bottom margin was already increased to hold page numbers + QRect newRect(x, y, rect.width(), rect.height()); + painter->drawText(newRect, page, *textOption); + painter->restore(); +} + +int PdfExport::getPageNumberHeight() +{ + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + painter->save(); + painter->setFont(italicFont); + int height = painter->boundingRect(QRect(0, 0, 1, 1), "0123456789", opt).height(); + painter->restore(); + return height; +} + +void PdfExport::exportDataHeader(const QString& contents) +{ + DataRow* row = new DataRow; + row->type = DataRow::Type::TOP_HEADER; + + DataCell cell; + cell.contents = contents; + cell.alignment = Qt::AlignHCenter; + row->cells << cell; + + headerRow.reset(row); +} + +void PdfExport::exportDataColumnsHeader(const QStringList& columns) +{ + DataRow* row = new DataRow; + row->type = DataRow::Type::COLUMNS_HEADER; + + DataCell cell; + cell.alignment = Qt::AlignHCenter; + for (const QString& col : columns) + { + cell.contents = col; + row->cells << cell; + } + + columnsHeaderRow.reset(row); +} + +void PdfExport::newPage() +{ + if (currentPage < 0) + { + currentPage = 0; + renderPageNumber(); + return; + } + + pagedWriter->newPage(); + currentPage++; + lastRowY = getContentsTop(); + renderPageNumber(); +} + +void PdfExport::calculateDataColumnWidths(const QStringList& columnNames, const QList& columnDataLengths, int columnToExpand) +{ + static const QString tplChar = QStringLiteral("W"); + + // Text options for calculating widths will not allow word wrapping + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + // Calculate header width first + if (columnToExpand > -1) + { + // If any column was picked for expanding table to page width, the header will also be full page width. + // This will also result later with expanding selected column to the header width = page width. + currentHeaderMinWidth = pageWidth; + } + else + { + currentHeaderMinWidth = 0; + if (headerRow) + { + painter->save(); + painter->setFont(boldFont); + currentHeaderMinWidth = painter->boundingRect(QRectF(0, 0, 1, 1), headerRow->cells.first().contents, opt).width(); + currentHeaderMinWidth += padding * 2; + painter->restore(); + } + } + + // Calculate width of rowNum column (if enabled) + rowNumColumnWidth = 0; + if (printRowNum) + rowNumColumnWidth = painter->boundingRect(QRectF(0, 0, 1, 1), QString::number(totalRows), opt).width() + 2 * padding; + + // Precalculate column widths for the header row + QList headerWidths; + for (const QString& colName : columnNames) + headerWidths << painter->boundingRect(QRectF(0, 0, 1, 1), colName, opt).width(); + + // Calculate width for each column and compare it with its header width, then pick the wider, but never wider than the maximum width. + calculatedDataColumnWidths.clear(); + int dataWidth = 0; + int headerWidth = 0; + int totalWidth = 0; + for (int i = 0, total = columnDataLengths.size(); i < total; ++i) + { + dataWidth = painter->boundingRect(QRectF(0, 0, 1, 1), tplChar.repeated(columnDataLengths[i]), opt).width(); + headerWidth = headerWidths[i]; + + // Pick the wider one, but never wider than maxColWidth + totalWidth = qMax(dataWidth, headerWidth) + padding * 2; // wider one + padding on sides + calculatedDataColumnWidths << qMin(maxColWidth, totalWidth); + } + + // Calculate how many columns will fit for every page, until the full row is rendered. + columnsPerPage.clear(); + int colsForThePage = 0; + int currentTotalWidth = 0; + int expandColumnIndex = 0; + int dataColumnsWidth = getDataColumnsWidth(); + for (int i = 0, total = calculatedDataColumnWidths.size(); i < total; ++i) + { + colsForThePage++; + currentTotalWidth += calculatedDataColumnWidths[i]; + if (currentTotalWidth > dataColumnsWidth) + { + colsForThePage--; + columnsPerPage << colsForThePage; + + // Make sure that columns on previous page are at least as wide as the header + currentTotalWidth -= calculatedDataColumnWidths[i]; + if ((currentTotalWidth + rowNumColumnWidth) < currentHeaderMinWidth && i > 0) + { + expandColumnIndex = 1; + if (columnToExpand > -1) + expandColumnIndex = colsForThePage - columnToExpand; + + calculatedDataColumnWidths[i - expandColumnIndex] += (currentHeaderMinWidth - (currentTotalWidth + rowNumColumnWidth)); + } + + // Reset values fot next interation + currentTotalWidth = calculatedDataColumnWidths[i]; + colsForThePage = 1; + } + } + + if (colsForThePage > 0) + { + columnsPerPage << colsForThePage; + if ((currentTotalWidth + rowNumColumnWidth) < currentHeaderMinWidth && !calculatedDataColumnWidths.isEmpty()) + { + int i = calculatedDataColumnWidths.size(); + expandColumnIndex = 1; + if (columnToExpand > -1) + expandColumnIndex = colsForThePage - columnToExpand; + + calculatedDataColumnWidths[i - expandColumnIndex] += (currentHeaderMinWidth - (currentTotalWidth + rowNumColumnWidth)); + } + } +} + +void PdfExport::calculateDataRowHeights() +{ + // Calculating heights for data rows + int thisRowMaxHeight = 0; + int actualColHeight = 0; + for (DataRow& row : bufferedDataRows) + { + if (row.height > 0) // was calculated in the previous rendering iteration + continue; + + thisRowMaxHeight = 0; + for (int col = 0, total = row.cells.size(); col < total; ++col) + { + // We pass rect, that is as wide as calculated column width and we look how height extends + actualColHeight = calculateRowHeight(calculatedDataColumnWidths[col], row.cells[col].contents); + thisRowMaxHeight = qMax(thisRowMaxHeight, actualColHeight); + } + row.height = qMin(maxRowHeight, thisRowMaxHeight); + } + + // Calculating heights for header rows + totalHeaderRowsHeight = 0; + if (headerRow) + { + painter->save(); + painter->setFont(boldFont); + // Main header can be as wide as page, so that's the rect we pass + actualColHeight = calculateRowHeight(pageWidth, headerRow->cells.first().contents); + + headerRow->height = qMin(maxRowHeight, actualColHeight); + totalHeaderRowsHeight += headerRow->height; + painter->restore(); + } + + if (columnsHeaderRow) + { + thisRowMaxHeight = 0; + for (int col = 0, total = columnsHeaderRow->cells.size(); col < total; ++col) + { + // This is the same as for data rows (see above) + actualColHeight = calculateRowHeight(calculatedDataColumnWidths[col], columnsHeaderRow->cells[col].contents); + thisRowMaxHeight = qMax(thisRowMaxHeight, actualColHeight); + } + + columnsHeaderRow->height = qMin(maxRowHeight, thisRowMaxHeight); + totalHeaderRowsHeight += columnsHeaderRow->height; + } + +} + +int PdfExport::calculateRowHeight(int maxTextWidth, const QString& contents) +{ + // Measures height expanding due to constrained text width, line wrapping and top+bottom padding + return painter->boundingRect(QRect(0, 0, (maxTextWidth - padding * 2), 1), contents, *textOption).height() + padding * 2; +} + +int PdfExport::getDataColumnsWidth() const +{ + if (printRowNum) + return pageWidth - rowNumColumnWidth; + + return pageWidth; +} + +int PdfExport::getDataColumnsStartX() const +{ + if (printRowNum) + return getContentsLeft() + rowNumColumnWidth; + + return getContentsLeft(); +} + +int PdfExport::getContentsLeft() const +{ + return leftMargin; +} + +int PdfExport::getContentsTop() const +{ + return topMargin; +} + +int PdfExport::getContentsRight() const +{ + return getContentsLeft() + pageWidth; +} + +int PdfExport::getContentsBottom() const +{ + return topMargin + pageHeight; +} + +qreal PdfExport::mmToPoints(qreal sizeMM) +{ + return pointsPerMm * sizeMM; +} + +CfgMain* PdfExport::getConfig() +{ + return &cfg; +} + +QString PdfExport::getExportConfigFormName() const +{ + return "PdfExportConfig"; +} + +QFont Cfg::getPdfExportDefaultFont() +{ + QPainter p; + return p.font(); +} + +QStringList Cfg::getPdfPageSizes() +{ + return getAllPageSizes(); +} diff --git a/Plugins/PdfExport/pdfexport.h b/Plugins/PdfExport/pdfexport.h new file mode 100644 index 0000000..9c7cc62 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.h @@ -0,0 +1,225 @@ +#ifndef PDFEXPORT_H +#define PDFEXPORT_H + +#include "pdfexport_global.h" +#include "plugins/genericexportplugin.h" +#include "config_builder.h" +#include +#include +#include + +class QPagedPaintDevice; +class QPainter; +class QTextOption; +class QFont; + +namespace Cfg +{ + PDFEXPORTSHARED_EXPORT QFont getPdfExportDefaultFont(); + PDFEXPORTSHARED_EXPORT QStringList getPdfPageSizes(); +} + +CFG_CATEGORIES(PdfExportConfig, + CFG_CATEGORY(PdfExport, + CFG_ENTRY(QString, PageSize, "A4") + CFG_ENTRY(QStringList, PageSizes, Cfg::getPdfPageSizes()) + CFG_ENTRY(int, Padding, 1) + CFG_ENTRY(bool, PrintRowNum, true) + CFG_ENTRY(bool, PrintPageNumbers, true) + CFG_ENTRY(int, TopMargin, 20) + CFG_ENTRY(int, RightMargin, 20) + CFG_ENTRY(int, BottomMargin, 20) + CFG_ENTRY(int, LeftMargin, 20) + CFG_ENTRY(int, MaxCellBytes, 100) + CFG_ENTRY(QFont, Font, Cfg::getPdfExportDefaultFont()) + CFG_ENTRY(int, FontSize, 10) + CFG_ENTRY(QColor, HeaderBgColor, QColor(Qt::lightGray)) + CFG_ENTRY(QColor, NullValueColor, QColor(Qt::gray)) + ) +) + +class PDFEXPORTSHARED_EXPORT PdfExport : public GenericExportPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("pdfexport.json") + + public: + QString getFormatName() const; + ExportManager::StandardConfigFlags standardOptionsToEnable() const; + ExportManager::ExportProviderFlags getProviderFlags() const; + void validateOptions(); + CfgMain* getConfig(); + QString getExportConfigFormName() const; + QString defaultFileExtension() const; + bool beforeExportQueryResults(const QString& query, QList& columns, + const QHash providedData); + bool exportQueryResultsRow(SqlResultsRowPtr row); + bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash providedData); + bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, + const QHash providedData); + bool afterExport(); + bool afterExportTable(); + bool afterExportQueryResults(); + bool exportTableRow(SqlResultsRowPtr data); + bool beforeExportDatabase(const QString& database); + bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex); + bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger); + bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view); + void cleanupAfterExport(); + bool isBinaryData() const; + bool init(); + void deinit(); + + protected: + virtual QPagedPaintDevice* createPaintDevice(const QString& documentTitle); + + int lineWidth = 15; + + private: + struct DataCell + { + QString contents; + Qt::Alignment alignment = Qt::AlignLeft; + bool isNull = false; + bool isRowNum = false; + }; + + struct DataRow + { + enum class Type + { + NORMAL, + TOP_HEADER, + COLUMNS_HEADER + }; + + QList cells; + int height = 0; + Type type = Type::NORMAL; + }; + + struct ObjectCell + { + enum class Type + { + NORMAL, + LIST + }; + + QStringList contents; + Qt::Alignment alignment = Qt::AlignLeft; + bool headerBackground = false; + bool bold = false; + bool italic = false; + Type type = Type::NORMAL; + }; + + struct ObjectRow + { + enum class Type + { + MULTI, + SINGLE + }; + + QList cells; + int height = 0; + Type type = Type::SINGLE; + bool recalculateColumnWidths = false; + }; + + void prepareTableDataExport(const QString& table, const QStringList& columnNames, const QHash providedData); + QList getColumnDataLengths(int columnCount, const QHash providedData); + bool beginDoc(const QString& title); + void endDoc(); + void setupConfig(); + void updateMargins(); + void drawDdl(const QString& objName, const QString& ddl); + void clearDataHeaders(); + void resetDataTable(); + void exportDataHeader(const QString& contents); + void exportDataColumnsHeader(const QStringList& columns); + void exportDataRow(const QList& data); + void exportObjectHeader(const QString& contents); + void exportObjectColumnsHeader(const QStringList& columns); + void exportTableColumnRow(SqliteCreateTable::Column* column); + void exportTableConstraintsRow(const QList& constrList); + void exportObjectRow(const QStringList& values); + void exportObjectRow(const QString& values); + void checkForDataRender(); + void flushObjectPages(); + void drawObjectTopLine(int y); + void drawObjectCellHeaderBackground(int x1, int y1, int x2, int y2); + void drawFooter(); + void flushObjectRow(const ObjectRow& row, int y); + void flushObjectCell(const ObjectCell& cell, int x, int y, int w, int h); + void flushDataPages(bool forceRender = false); + void flushDataRowsPage(int columnStart, int columnEndBefore, int rowsToRender); + void flushDataRow(const DataRow& row, int& y, int columnStart, int columnEndBefore, int localRowNum); + void flushDataCell(const QRect& rect, const DataCell& cell); + void flushDataCell(const QRect& rect, const QString& contents, QTextOption* opt); + void flushDataHeaderRow(const DataRow& row, int& y, int totalColsWidth, int columnStart, int columnEndBefore); + void flushDataHeaderCell(int& x, int y, const DataRow& row, int col, QTextOption* opt); + void renderPageNumber(); + int getPageNumberHeight(); + void newPage(); + void calculateDataColumnWidths(const QStringList& columnNames, const QList& columnDataLengths, int columnToExpand = -1); + void calculateDataRowHeights(); + int calculateRowHeight(int textWidth, const QString& contents); + int calculateRowHeight(int maxTextWidth, const QStringList& listContents); + int calculateBulletPrefixWidth(); + void calculateObjectColumnWidths(int columnToExpand = -1); + int correctMaxObjectColumnWidths(int colCount, int columnToExpand); + void calculateObjectRowHeights(); + int getDataColumnsWidth() const; + int getDataColumnsStartX() const; + int getContentsLeft() const; + int getContentsTop() const; + int getContentsRight() const; + int getContentsBottom() const; + qreal mmToPoints(qreal sizeMM); + + CFG_LOCAL(PdfExportConfig, cfg) + QPagedPaintDevice* pagedWriter = nullptr; + QPainter* painter = nullptr; + QTextOption* textOption = nullptr; + QFont stdFont; + QFont boldFont; + QFont italicFont; + int totalRows = 0; + QList bufferedObjectRows; // object rows (for exporting ddl of all object) + QList bufferedDataRows; // data rows + int totalHeaderRowsHeight = 0; // total height of all current header rows + int currentHeaderMinWidth = 0; // minimum width of the object header, required to extend data columns when hey are slimmer than this + QList calculatedObjectColumnWidths; // object column widths calculated basing on header column widths and columns from other object rows + QList calculatedDataColumnWidths; // data column widths calculated basing on header column widths and data column widths + QList columnsPerPage; // number of columns that will fit on each page + QScopedPointer headerRow; // Top level header (object name) + QScopedPointer columnsHeaderRow; // columns header for data tables + int rowNumColumnWidth = 0; + int pageWidth = 0; + int pageHeight = 0; + int minRowHeight = 0; + int rowsToPrebuffer = 0; + int currentPage = -1; + int rowNum = 0; + int lastRowY = 0; + qreal pointsPerMm = 1.0; + int maxColWidth = 0; + int maxRowHeight = 0; + int cellDataLimit = 100; + + static QString bulletChar; + + // Configurable fields + int padding = 0; + bool printRowNum = true; + bool printPageNumbers = true; + int topMargin = 0; + int rightMargin = 0; + int leftMargin = 0; + int bottomMargin = 0; +}; + +#endif // PDFEXPORT_H diff --git a/Plugins/PdfExport/pdfexport.json b/Plugins/PdfExport/pdfexport.json new file mode 100644 index 0000000..5f1e2e4 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.json @@ -0,0 +1,8 @@ +{ + "type": "ExportPlugin", + "title": "PDF export", + "description": "Provides PDF format for exporting.", + "version": 10000, + "author": "SalSoft", + "gui": true +} diff --git a/Plugins/PdfExport/pdfexport.qrc b/Plugins/PdfExport/pdfexport.qrc new file mode 100644 index 0000000..fce27e6 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.qrc @@ -0,0 +1,5 @@ + + + pdfexport.ui + + diff --git a/Plugins/PdfExport/pdfexport.ui b/Plugins/PdfExport/pdfexport.ui new file mode 100644 index 0000000..31dfce5 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.ui @@ -0,0 +1,303 @@ + + + PdfExportConfig + + + + 0 + 0 + 332 + 492 + + + + Form + + + + + + Size and layout + + + + + + 999 + + + PdfExport.MaxCellBytes + + + + + + + Page size: + + + + + + + 999999 + + + PdfExport.BottomMargin + + + + + + + 999999 + + + PdfExport.RightMargin + + + + + + + Right margin: + + + + + + + Left margin: + + + + + + + Cell padding: + + + + + + + Limit characters in single cell: + + + + + + + mm + + + + + + + 999999 + + + PdfExport.Padding + + + + + + + mm + + + + + + + mm + + + + + + + 999999 + + + PdfExport.TopMargin + + + + + + + mm + + + + + + + Bottom margin: + + + + + + + PdfExport.PageSizes + + + PdfExport.PageSize + + + + + + + mm + + + + + + + Top margin: + + + + + + + 999999 + + + PdfExport.LeftMargin + + + + + + + + + + Font + + + + + + PdfExport.FontSize + + + + + + + PdfExport.Font + + + + + + + + + + Colors + + + + + + Headers background: + + + + + + + + 50 + 16777215 + + + + + + + PdfExport.HeaderBgColor + + + + + + + NULL value color: + + + + + + + + 50 + 16777215 + + + + + + + PdfExport.NullValueColor + + + + + + + + + + Other settings + + + + + + Print row numbers for data + + + PdfExport.PrintRowNum + + + + + + + Print page numbers + + + PdfExport.PrintPageNumbers + + + + + + + + + + + ConfigComboBox + QComboBox +
common/configcombobox.h
+
+ + ColorButton + QPushButton +
common/colorbutton.h
+
+
+ + +
diff --git a/Plugins/PdfExport/pdfexport_global.h b/Plugins/PdfExport/pdfexport_global.h new file mode 100644 index 0000000..c30f2b5 --- /dev/null +++ b/Plugins/PdfExport/pdfexport_global.h @@ -0,0 +1,12 @@ +#ifndef PDFEXPORT_GLOBAL_H +#define PDFEXPORT_GLOBAL_H + +#include + +#if defined(PDFEXPORT_LIBRARY) +# define PDFEXPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define PDFEXPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // PDFEXPORT_GLOBAL_H diff --git a/Plugins/Plugins.pro b/Plugins/Plugins.pro new file mode 100644 index 0000000..805521a --- /dev/null +++ b/Plugins/Plugins.pro @@ -0,0 +1,19 @@ +TEMPLATE = subdirs + +pluginsProject = 1 + +SUBDIRS += \ + CsvExport \ + CsvImport \ + DbSqlite2 \ + HtmlExport \ + PdfExport \ + SqlExport \ + SqlFormatterSimple \ + XmlExport \ + JsonExport \ + RegExpImport \ + Printing \ + SqlEnterpriseFormatter \ + ConfigMigration \ + ScriptingTcl diff --git a/Plugins/Printing/Printing.pro b/Plugins/Printing/Printing.pro new file mode 100644 index 0000000..49469c2 --- /dev/null +++ b/Plugins/Printing/Printing.pro @@ -0,0 +1,35 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-08-03T20:25:45 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT += printsupport + +TARGET = Printing +TEMPLATE = lib + +DEFINES += PRINTING_LIBRARY + +SOURCES += printing.cpp \ + printingexport.cpp + +HEADERS += printing.h\ + printing_global.h \ + printingexport.h + +OTHER_FILES += \ + printing.json + +INCLUDEPATH += $$PLUGINSDIR/PdfExport +DEPENDPATH += $$PLUGINSDIR/PdfExport + +win32|macx: { +# LIBS += -lPdfExport + pluginDep(PdfExport) +} + +RESOURCES += \ + printing.qrc diff --git a/Plugins/Printing/printer.png b/Plugins/Printing/printer.png new file mode 100644 index 0000000..9edfbbc Binary files /dev/null and b/Plugins/Printing/printer.png differ diff --git a/Plugins/Printing/printing.cpp b/Plugins/Printing/printing.cpp new file mode 100644 index 0000000..ff8443d --- /dev/null +++ b/Plugins/Printing/printing.cpp @@ -0,0 +1,111 @@ +#include "printing.h" +#include "printingexport.h" +#include "common/unused.h" +#include "mainwindow.h" +#include "windows/editorwindow.h" +#include "dataview.h" +#include "common/extactionprototype.h" +#include "datagrid/sqlquerymodel.h" +#include "exportworker.h" +#include "services/notifymanager.h" +#include "sqleditor.h" +#include +#include +#include +#include + +bool Printing::init() +{ + Q_INIT_RESOURCE(printing); + + printingExport = new PrintingExport(); + bool printingExportInit = printingExport->init(); + if (!printingExportInit) + return false; + + printingConfig = new ExportManager::StandardExportConfig(); + printingConfig->exportData = true; + printingConfig->exportTableIndexes = false; + printingConfig->exportTableTriggers = false; + printingConfig->codec = defaultCodecName(); + + printDataAction = new ExtActionPrototype(QIcon(":/icons/printer.png"), tr("Print data"), this); + separatorAction = new ExtActionPrototype(this); + printQueryAction = new ExtActionPrototype(QIcon(":/icons/printer.png"), tr("Print query"), this); + + connect(printDataAction, SIGNAL(triggered(ExtActionContainer*,int)), this, SLOT(dataPrintRequested(ExtActionContainer*))); + connect(printQueryAction, SIGNAL(triggered(ExtActionContainer*,int)), this, SLOT(queryPrintRequested(ExtActionContainer*))); + + DataView::insertActionAfter(printDataAction, DataView::LAST_PAGE); + DataView::insertActionAfter(separatorAction, DataView::LAST_PAGE); + EditorWindow::insertActionAfter(printQueryAction, EditorWindow::EXPORT_RESULTS); + + return true; +} + +void Printing::deinit() +{ + printingExport->deinit(); + + DataView::removeAction(printDataAction); + DataView::removeAction(separatorAction); + EditorWindow::removeAction(printQueryAction); + safe_delete(printingExport); + safe_delete(printDataAction); + safe_delete(separatorAction); + safe_delete(printQueryAction); + safe_delete(printDialog); + Q_CLEANUP_RESOURCE(printing); +} + +void Printing::dataPrintRequested(ExtActionContainer* actionContainer) +{ + DataView* dataView = dynamic_cast(actionContainer); + if (!dataView) + { + qCritical() << "Printing::dataPrintRequested() called not from DataView:" << actionContainer; + return; + } + + if (dataView->getModel()->rowCount() == 0) + { + notifyError(tr("No data to print.")); + return; + } + + safe_delete(printDialog); + printDialog = new QPrintDialog(MAINWINDOW); + if (printDialog->exec() != QDialog::Accepted) + return; + + notifyInfo(tr("Printing data.")); + + QString query = dataView->getModel()->getQuery(); + Db* db = dataView->getModel()->getDb(); + + printingExport->setPaintDevice(printDialog->printer()); + + ExportWorker* worker = new ExportWorker(printingExport, printingConfig, nullptr); + worker->prepareExportQueryResults(db, query); + QThreadPool::globalInstance()->start(worker); +} + +void Printing::queryPrintRequested(ExtActionContainer* actionContainer) +{ + EditorWindow* editor = dynamic_cast(actionContainer); + if (!editor) + { + qCritical() << "Printing::queryPrintRequested() called not from EditorWindow:" << actionContainer; + return; + } + + safe_delete(printDialog); + printDialog = new QPrintDialog(MAINWINDOW); + if (printDialog->exec() != QDialog::Accepted) + return; + + notifyInfo(tr("Printing query.")); + + QTextDocument* doc = editor->getEditor()->document(); + doc->print(printDialog->printer()); +} diff --git a/Plugins/Printing/printing.h b/Plugins/Printing/printing.h new file mode 100644 index 0000000..78463a9 --- /dev/null +++ b/Plugins/Printing/printing.h @@ -0,0 +1,36 @@ +#ifndef PRINTING_H +#define PRINTING_H + +#include "printing_global.h" +#include "plugins/genericplugin.h" +#include "plugins/generalpurposeplugin.h" +#include "services/exportmanager.h" + +class ExtActionPrototype; +class ExtActionContainer; +class PrintingExport; +class QPrintDialog; + +class PRINTINGSHARED_EXPORT Printing : public GenericPlugin, public GeneralPurposePlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("printing.json") + + public: + bool init(); + void deinit(); + + private: + ExtActionPrototype* separatorAction = nullptr; + ExtActionPrototype* printDataAction = nullptr; + ExtActionPrototype* printQueryAction = nullptr; + PrintingExport* printingExport = nullptr; + ExportManager::StandardExportConfig* printingConfig = nullptr; + QPrintDialog* printDialog = nullptr; + + private slots: + void dataPrintRequested(ExtActionContainer* actionContainer); + void queryPrintRequested(ExtActionContainer* actionContainer); +}; + +#endif // PRINTING_H diff --git a/Plugins/Printing/printing.json b/Plugins/Printing/printing.json new file mode 100644 index 0000000..1a59fb5 --- /dev/null +++ b/Plugins/Printing/printing.json @@ -0,0 +1,9 @@ +{ + "type": "GeneralPurposePlugin", + "title": "Printing", + "description": "Provides printing support.", + "version": 10000, + "author": "SalSoft", + "gui": true, + "dependencies": "PdfExport" +} diff --git a/Plugins/Printing/printing.qrc b/Plugins/Printing/printing.qrc new file mode 100644 index 0000000..faf09c1 --- /dev/null +++ b/Plugins/Printing/printing.qrc @@ -0,0 +1,5 @@ + + + printer.png + + diff --git a/Plugins/Printing/printing_global.h b/Plugins/Printing/printing_global.h new file mode 100644 index 0000000..59be5a4 --- /dev/null +++ b/Plugins/Printing/printing_global.h @@ -0,0 +1,12 @@ +#ifndef PRINTING_GLOBAL_H +#define PRINTING_GLOBAL_H + +#include + +#if defined(PRINTING_LIBRARY) +# define PRINTINGSHARED_EXPORT Q_DECL_EXPORT +#else +# define PRINTINGSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // PRINTING_GLOBAL_H diff --git a/Plugins/Printing/printingexport.cpp b/Plugins/Printing/printingexport.cpp new file mode 100644 index 0000000..c2d7fd0 --- /dev/null +++ b/Plugins/Printing/printingexport.cpp @@ -0,0 +1,46 @@ +#include "printingexport.h" +#include "common/unused.h" +#include "mainwindow.h" +#include "services/notifymanager.h" + +QPagedPaintDevice* PrintingExport::createPaintDevice(const QString& documentTitle) +{ + UNUSED(documentTitle); + return paintDevice; +} + +QPagedPaintDevice* PrintingExport::getPaintDevice() const +{ + return paintDevice; +} + +void PrintingExport::setPaintDevice(QPagedPaintDevice* value) +{ + paintDevice = value; +} + +bool PrintingExport::init() +{ + lineWidth = 1; + return PdfExport::init(); +} + +void PrintingExport::deinit() +{ +} + +QString PrintingExport::getFormatName() const +{ + return tr("Printing"); +} + +ExportManager::StandardConfigFlags PrintingExport::standardOptionsToEnable() const +{ + return 0; +} + +ExportManager::ExportModes PrintingExport::getSupportedModes() const +{ + return ExportManager::DATABASE|ExportManager::TABLE|ExportManager::QUERY_RESULTS; + +} diff --git a/Plugins/Printing/printingexport.h b/Plugins/Printing/printingexport.h new file mode 100644 index 0000000..fc6172d --- /dev/null +++ b/Plugins/Printing/printingexport.h @@ -0,0 +1,28 @@ +#ifndef PRINTINGEXPORT_H +#define PRINTINGEXPORT_H + +#include "printing_global.h" +#include "PdfExport/pdfexport.h" + +class PRINTINGSHARED_EXPORT PrintingExport : public PdfExport +{ + Q_OBJECT + + public: + bool init(); + void deinit(); + QString getFormatName() const; + ExportManager::StandardConfigFlags standardOptionsToEnable() const; + ExportManager::ExportModes getSupportedModes() const; + + QPagedPaintDevice* getPaintDevice() const; + void setPaintDevice(QPagedPaintDevice* value); + + protected: + QPagedPaintDevice* createPaintDevice(const QString& documentTitle); + + private: + QPagedPaintDevice* paintDevice = nullptr; +}; + +#endif // PRINTINGEXPORT_H diff --git a/Plugins/RegExpImport/RegExpImport.pro b/Plugins/RegExpImport/RegExpImport.pro new file mode 100644 index 0000000..99c884e --- /dev/null +++ b/Plugins/RegExpImport/RegExpImport.pro @@ -0,0 +1,28 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-07-20T12:19:38 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = RegExpImport +TEMPLATE = lib + +DEFINES += REGEXPIMPORT_LIBRARY + +SOURCES += regexpimport.cpp + +HEADERS += regexpimport.h\ + regexpimport_global.h + +OTHER_FILES += \ + regexpimport.json + +FORMS += \ + regexpimport.ui + +RESOURCES += \ + regexpimport.qrc diff --git a/Plugins/RegExpImport/regexpimport.cpp b/Plugins/RegExpImport/regexpimport.cpp new file mode 100644 index 0000000..cd761b1 --- /dev/null +++ b/Plugins/RegExpImport/regexpimport.cpp @@ -0,0 +1,209 @@ +#include "regexpimport.h" +#include "services/notifymanager.h" +#include "common/utils.h" +#include "services/importmanager.h" +#include "sqlitestudio.h" +#include +#include +#include + +RegExpImport::RegExpImport() +{ +} + +bool RegExpImport::init() +{ + Q_INIT_RESOURCE(regexpimport); + return GenericPlugin::init(); +} + +void RegExpImport::deinit() +{ + Q_CLEANUP_RESOURCE(regexpimport); +} + +QString RegExpImport::getDataSourceTypeName() const +{ + return "RegExp"; +} + +ImportManager::StandardConfigFlags RegExpImport::standardOptionsToEnable() const +{ + return ImportManager::CODEC|ImportManager::FILE_NAME; +} + +QString RegExpImport::getFileFilter() const +{ + return tr("Text files (*.txt);;All files (*)"); +} + +bool RegExpImport::beforeImport(const ImportManager::StandardImportConfig& config) +{ + safe_delete(re); + safe_delete(file); + safe_delete(stream); + groups.clear(); + buffer.clear(); + columns.clear(); + + + file = new QFile(config.inputFileName); + if (!file->open(QFile::ReadOnly) || !file->isReadable()) + { + notifyError(tr("Cannot read file %1").arg(config.inputFileName)); + safe_delete(file); + return false; + } + + stream = new QTextStream(file); + stream->setCodec(config.codec.toLatin1().data()); + + + static const QString intColTemplate = QStringLiteral("column%1"); + re = new QRegularExpression(cfg.RegExpImport.Pattern.get()); + QString colName; + if (cfg.RegExpImport.GroupsMode.get() == "all") + { + for (int i = 1; i <= re->captureCount(); i++) + { + groups << i; + colName = intColTemplate.arg(i); + columns << generateUniqueName(colName, columns); + } + } + else + { + QStringList entries = cfg.RegExpImport.CustomGroupList.get().split(QRegularExpression(",\\s*")); + int i; + bool ok; + for (const QString& entry : entries) + { + i = entry.toInt(&ok); + if (ok) + { + groups << i; + colName = intColTemplate.arg(i); + } + else + { + groups << entry; + colName = entry; + } + columns << generateUniqueName(colName, columns); + } + } + return true; +} + +void RegExpImport::afterImport() +{ + safe_delete(re); + safe_delete(file); + safe_delete(stream); + buffer.clear(); + groups.clear(); +} + +QList RegExpImport::getColumns() const +{ + QList columnList; + for (const QString& colName : columns) + columnList << ImportPlugin::ColumnDefinition(colName, QString()); + + return columnList; +} + +QList RegExpImport::next() +{ + QRegularExpressionMatch match = re->match(buffer); + QString line; + while (!match.hasMatch() && !(line = stream->readLine()).isNull()) + { + buffer += line; + match = re->match(buffer); + } + + if (!match.hasMatch()) + return QList(); + + QList values; + for (const QVariant& group : groups) + { + if (group.type() == QVariant::Int) + values << match.captured(group.toInt()); + else + values << match.captured(group.toString()); + } + + buffer = buffer.mid(match.capturedEnd()); + + return values; +} + +CfgMain* RegExpImport::getConfig() +{ + return &cfg; +} + +QString RegExpImport::getImportConfigFormName() const +{ + return "RegExpImportConfig"; +} + +bool RegExpImport::validateOptions() +{ + QString reMsg; + QString pattern = cfg.RegExpImport.Pattern.get(); + bool reOk = true; + if (pattern.isEmpty()) + { + reOk = false; + reMsg = tr("Enter the regular expression pattern."); + } + + QRegularExpression localRe(pattern); + if (reOk) + { + reOk &= localRe.isValid(); + if (!reOk) + reMsg = tr("Invalid pattern: %1").arg(localRe.errorString()); + } + + QString groupMsg; + bool groupsOk = true; + bool isCustom = (cfg.RegExpImport.GroupsMode.get() == "custom"); + if (isCustom && reOk) + { + QStringList entries = cfg.RegExpImport.CustomGroupList.get().split(QRegularExpression(",\\s*")); + QStringList namedCaptureGroups = localRe.namedCaptureGroups(); + int captureCount = localRe.captureCount(); + int i; + bool ok; + for (const QString& entry : entries) + { + i = entry.toInt(&ok); + if (ok) + { + if (i < 0 || i > captureCount) + { + groupMsg = tr("Requested capture index %1 is out of range.").arg(i); + groupsOk = false; + break; + } + } + else if (!namedCaptureGroups.contains(entry)) + { + groupMsg = tr("

Requested capture group name '%1', but it's not defined in the pattern:

%2

") + .arg(entry, pattern.toHtmlEscaped()); + groupsOk = false; + break; + } + } + } + + IMPORT_MANAGER->handleValidationFromPlugin(reOk, cfg.RegExpImport.Pattern, reMsg); + IMPORT_MANAGER->handleValidationFromPlugin(groupsOk, cfg.RegExpImport.CustomGroupList, groupMsg); + IMPORT_MANAGER->updateVisibilityAndEnabled(cfg.RegExpImport.CustomGroupList, true, isCustom); + + return reOk && groupsOk; +} diff --git a/Plugins/RegExpImport/regexpimport.h b/Plugins/RegExpImport/regexpimport.h new file mode 100644 index 0000000..3603401 --- /dev/null +++ b/Plugins/RegExpImport/regexpimport.h @@ -0,0 +1,52 @@ +#ifndef REGEXPIMPORT_H +#define REGEXPIMPORT_H + +#include "regexpimport_global.h" +#include "plugins/genericplugin.h" +#include "plugins/importplugin.h" +#include "config_builder.h" + +class QRegularExpression; +class QFile; +class QTextStream; + +CFG_CATEGORIES(RegExpImportConfig, + CFG_CATEGORY(RegExpImport, + CFG_ENTRY(QString, Pattern, QString()) + CFG_ENTRY(QString, GroupsMode, "all") // all / custom + CFG_ENTRY(QString, CustomGroupList, QString()) + ) +) + +class REGEXPIMPORTSHARED_EXPORT RegExpImport : public GenericPlugin, public ImportPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("regexpimport.json") + + public: + RegExpImport(); + + bool init(); + void deinit(); + QString getDataSourceTypeName() const; + ImportManager::StandardConfigFlags standardOptionsToEnable() const; + QString getFileFilter() const; + bool beforeImport(const ImportManager::StandardImportConfig& config); + void afterImport(); + QList getColumns() const; + QList next(); + CfgMain* getConfig(); + QString getImportConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(RegExpImportConfig, cfg) + QRegularExpression* re = nullptr; + QList groups; + QStringList columns; + QFile* file = nullptr; + QTextStream* stream = nullptr; + QString buffer; +}; + +#endif // REGEXPIMPORT_H diff --git a/Plugins/RegExpImport/regexpimport.json b/Plugins/RegExpImport/regexpimport.json new file mode 100644 index 0000000..8a0f746 --- /dev/null +++ b/Plugins/RegExpImport/regexpimport.json @@ -0,0 +1,7 @@ +{ + "type": "ImportPlugin", + "title": "RegExp import", + "description": "Importing data from text files using regular expression.", + "version": 10000, + "author": "SalSoft" +} diff --git a/Plugins/RegExpImport/regexpimport.qrc b/Plugins/RegExpImport/regexpimport.qrc new file mode 100644 index 0000000..9ece86e --- /dev/null +++ b/Plugins/RegExpImport/regexpimport.qrc @@ -0,0 +1,5 @@ + + + regexpimport.ui + + diff --git a/Plugins/RegExpImport/regexpimport.ui b/Plugins/RegExpImport/regexpimport.ui new file mode 100644 index 0000000..d1287cb --- /dev/null +++ b/Plugins/RegExpImport/regexpimport.ui @@ -0,0 +1,99 @@ + + + RegExpImportConfig + + + + 0 + 0 + 400 + 133 + + + + Form + + + + + + Capture groups + + + + + + Treat all RegExp capture groups as columns + + + all + + + RegExpImport.GroupsMode + + + + + + + Import only following groups: + + + custom + + + RegExpImport.GroupsMode + + + + + + + <p>Enter comma separated list of capture group indexes. The 0 index refers to the entire matched string.</p> +<p>If you used named groups in the pattern, you can use names instead of indexes. You can mix indexes and names in this list.</p> + + + Example: 1, 3, 4 + + + RegExpImport.CustomGroupList + + + + + + + + + + Pattern: + + + + + + + <p>Use Regular Expression groups to enclose parts of the expression that you want to import. If you want to use a group, that you don't want to import, then use "import only following groups" option below. + +You can use named groups and refer to them in group list below. To name a group use: <pre>(?&lt;myGroupName&gt;\s+\d+\s+)</pre></p> + + + Example: (\d+)\s+((\d+)\w+)\s+(\w+) + + + RegExpImport.Pattern + + + + + + + + ConfigRadioButton + QRadioButton +
common/configradiobutton.h
+
+
+ + +
diff --git a/Plugins/RegExpImport/regexpimport_global.h b/Plugins/RegExpImport/regexpimport_global.h new file mode 100644 index 0000000..c09b742 --- /dev/null +++ b/Plugins/RegExpImport/regexpimport_global.h @@ -0,0 +1,12 @@ +#ifndef REGEXPIMPORT_GLOBAL_H +#define REGEXPIMPORT_GLOBAL_H + +#include + +#if defined(REGEXPIMPORT_LIBRARY) +# define REGEXPIMPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define REGEXPIMPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // REGEXPIMPORT_GLOBAL_H diff --git a/Plugins/ScriptingTcl/ScriptingTcl.pro b/Plugins/ScriptingTcl/ScriptingTcl.pro new file mode 100644 index 0000000..4b89ce6 --- /dev/null +++ b/Plugins/ScriptingTcl/ScriptingTcl.pro @@ -0,0 +1,127 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-07-19T12:58:14 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = ScriptingTcl +TEMPLATE = lib + +DEFINES += SCRIPTINGTCL_LIBRARY + +SOURCES += scriptingtcl.cpp + +HEADERS += scriptingtcl.h\ + scriptingtcl_global.h + +OTHER_FILES += \ + scriptingtcl.json + +linux: { + # Find tclsh + TCLSH = $$system(echo "puts 1" | tclsh) + !contains(TCLSH, 1): { + error("Could not find tclsh executable. ScriptingTcl plugin requires it to find out all Tcl libraries and headers. Make tclsh available in PATH.") + } + TCLSH = $$system(which tclsh) + + # Find its version + TCL_VERSION = $$system(echo "puts [info tclversion]" | tclsh) + #message("Found tclsh: $$TCLSH (version: $$TCL_VERSION)") + + # Find tclConfig.sh + TCL_CONFIG_DIR = $$system(echo "puts [info library]" | tclsh) + TCL_CONFIG = $$TCL_CONFIG_DIR/tclConfig.sh + + # Define other libs required when linking with Tcl + eval($$system(cat $$TCL_CONFIG | grep TCL_LIBS)) + eval(LIBS += $$TCL_LIBS) + + # Define headers dir + eval($$system(cat $$TCL_CONFIG | grep TCL_INCLUDE_SPEC)) + INCLUDEPATH += $$replace(TCL_INCLUDE_SPEC, -I/, /) + DEPENDPATH += $$replace(TCL_INCLUDE_SPEC, -I/, /) + + # Find static library + eval($$system(cat $$TCL_CONFIG | grep TCL_STUB_LIB_PATH)) + STATIC_LIB = $$replace(TCL_STUB_LIB_PATH, tclstub, tcl) + + # If found static lib, we link statically + exists($$STATIC_LIB) { + #message("Static linking of libtcl: $$STATIC_LIB") + LIBS += $$STATIC_LIB + } + + # If not found, use dynamic linking flags + !exists($$STATIC_LIB) { + eval($$system(cat $$TCL_CONFIG | grep TCL_LIB_SPEC)) + #message("Dynamic linking of libtcl: $$TCL_LIB_SPEC") + eval(LIBS += $$TCL_LIB_SPEC) + } +} + +macx: { + # Find tclsh + TCLSH = $$system(echo "puts 1" | tclsh) + !contains(TCLSH, 1): { + error("Could not find tclsh executable. ScriptingTcl plugin requires it to find out all Tcl libraries and headers. Make tclsh available in PATH.") + } + TCLSH = $$system(which tclsh) + + # Find its version + TCL_VERSION = $$system(echo "puts [info tclversion]" | tclsh) + #message("Found tclsh: $$TCLSH (version: $$TCL_VERSION)") + + # Find tclConfig.sh + TCL_CONFIG_DIR = $$system(echo "puts [info library]" | tclsh) + TCL_CONFIG = $$TCL_CONFIG_DIR/../../tclConfig.sh + + # Define other libs required when linking with Tcl + eval($$system(cat $$TCL_CONFIG | grep TCL_LIBS)) + eval(LIBS += $$TCL_LIBS) + + # Define headers dir + eval($$system(cat $$TCL_CONFIG | grep TCL_INCLUDE_SPEC)) + INCLUDEPATH += $$replace(TCL_INCLUDE_SPEC, -I/, /) + DEPENDPATH += $$replace(TCL_INCLUDE_SPEC, -I/, /) + + # Find static library + eval($$system(cat $$TCL_CONFIG | grep TCL_STUB_LIB_PATH)) + STATIC_LIB = $$replace(TCL_STUB_LIB_PATH, tclstub, tcl) + + # If found static lib, we link statically + exists($$STATIC_LIB) { + #message("Static linking of libtcl: $$STATIC_LIB") + LIBS += $$STATIC_LIB + } + + # If not found, use dynamic linking flags + !exists($$STATIC_LIB) { + eval($$system(cat $$TCL_CONFIG | grep TCL_LIB_SPEC)) + #message("Dynamic linking of libtcl: $$TCL_LIB_SPEC") + eval(LIBS += $$TCL_LIB_SPEC) + } +} + +win32: { + # Under Windows we don't do the research. We just assume we have everything in the lib/ and include/ + # directories, which contain all other dependencies for SQLiteStudio. Get them from any Tcl installation you want. + # Lib files required for compilation of this plugin: + # - tcl86.lib + # - tcl86.dll + # Include files required for compilation: + # - tcl.h + # - tclDecls.h + # - tclPlatDecls.h + # Lib files required for the runtime in applications directory: + # - tcl86.dll + # The "86" part may vary, depending on Tcl version you're linking with. + LIBS += -ltcl86 +} + +RESOURCES += \ + scriptingtcl.qrc diff --git a/Plugins/ScriptingTcl/scriptingtcl.cpp b/Plugins/ScriptingTcl/scriptingtcl.cpp new file mode 100644 index 0000000..5709808 --- /dev/null +++ b/Plugins/ScriptingTcl/scriptingtcl.cpp @@ -0,0 +1,656 @@ +#include "scriptingtcl.h" +#include "common/global.h" +#include "common/unused.h" +#include "db/db.h" +#include "parser/lexer.h" +#include "parser/token.h" +#include "common/utils_sql.h" +#include +#include + +ScriptingTcl::ScriptingTcl() +{ + mainInterpMutex = new QMutex(); +} + +ScriptingTcl::~ScriptingTcl() +{ + safe_delete(mainInterpMutex); +} + +bool ScriptingTcl::init() +{ + Q_INIT_RESOURCE(scriptingtcl); + QMutexLocker locker(mainInterpMutex); + mainContext = new ContextTcl(); + return true; +} + +void ScriptingTcl::deinit() +{ + QMutexLocker locker(mainInterpMutex); + safe_delete(mainContext); + Tcl_Finalize(); + Q_CLEANUP_RESOURCE(scriptingtcl); +} + +QString ScriptingTcl::getLanguage() const +{ + return "Tcl"; +} + +ScriptingPlugin::Context* ScriptingTcl::createContext() +{ + ContextTcl* ctx = new ContextTcl(); + contexts << ctx; + return ctx; +} + +void ScriptingTcl::releaseContext(ScriptingPlugin::Context* context) +{ + ContextTcl* ctx = getContext(context); + if (!ctx) + return; + + contexts.removeOne(ctx); + delete ctx; +} + +void ScriptingTcl::resetContext(ScriptingPlugin::Context* context) +{ + ContextTcl* ctx = getContext(context); + if (!ctx) + return; + + ctx->reset(); +} + +void ScriptingTcl::setVariable(ScriptingPlugin::Context* context, const QString& name, const QVariant& value) +{ + ContextTcl* ctx = getContext(context); + if (!ctx) + return; + + setVariable(ctx->interp, name, value); +} + +QVariant ScriptingTcl::getVariable(ScriptingPlugin::Context* context, const QString& name) +{ + ContextTcl* ctx = getContext(context); + if (!ctx) + return QVariant(); + + return getVariable(ctx->interp, name); +} + +bool ScriptingTcl::hasError(ScriptingPlugin::Context* context) const +{ + ContextTcl* ctx = getContext(context); + if (!ctx) + return false; + + return !ctx->error.isEmpty(); +} + +QString ScriptingTcl::getErrorMessage(ScriptingPlugin::Context* context) const +{ + ContextTcl* ctx = getContext(context); + if (!ctx) + return QString(); + + return ctx->error; +} + +QString ScriptingTcl::getIconPath() const +{ + return ":/scriptingtcl/scriptingtcl.png"; +} + +QVariant ScriptingTcl::evaluate(ScriptingPlugin::Context* context, const QString& code, const QList& args, Db* db, bool locking) +{ + ContextTcl* ctx = getContext(context); + if (!ctx) + return QVariant(); + + setArgs(ctx, args); + return compileAndEval(ctx, code, db, locking); +} + +QVariant ScriptingTcl::evaluate(const QString& code, const QList& args, Db* db, bool locking, QString* errorMessage) +{ + QMutexLocker locker(mainInterpMutex); + setArgs(mainContext, args); + QVariant results = compileAndEval(mainContext, code, db, locking); + + if (errorMessage && !mainContext->error.isEmpty()) + *errorMessage = mainContext->error; + + return results; +} + +ScriptingTcl::ContextTcl* ScriptingTcl::getContext(ScriptingPlugin::Context* context) const +{ + ContextTcl* ctx = dynamic_cast(context); + if (!ctx) + qDebug() << "Invalid context passed to ScriptingTcl:" << context; + + return ctx; +} + +QVariant ScriptingTcl::compileAndEval(ScriptingTcl::ContextTcl* ctx, const QString& code, Db* db, bool locking) +{ + ScriptObject* scriptObj = nullptr; + if (!ctx->scriptCache.contains(code)) + { + scriptObj = new ScriptObject(code); + ctx->scriptCache.insert(code, scriptObj); + } + else + { + scriptObj = ctx->scriptCache[code]; + } + Tcl_ResetResult(ctx->interp); + ctx->error.clear(); + + ctx->db = db; + ctx->useDbLocking = locking; + + int result = Tcl_EvalObjEx(ctx->interp, scriptObj->getTclObj(), TCL_EVAL_GLOBAL); + + ctx->db = nullptr; + ctx->useDbLocking = false; + + if (result != TCL_OK) + { + ctx->error = QString::fromUtf8(Tcl_GetStringResult(ctx->interp)); + return QVariant(); + } + return extractResult(ctx); +} + +QVariant ScriptingTcl::extractResult(ScriptingTcl::ContextTcl* ctx) +{ + Tcl_Obj* obj = Tcl_GetObjResult(ctx->interp); + return tclObjToVariant(obj); +} + +void ScriptingTcl::setArgs(ScriptingTcl::ContextTcl* ctx, const QList& args) +{ + setVariable(ctx, "argc", args.size()); + setVariable(ctx, "argv", args); +} + +Tcl_Obj* ScriptingTcl::argsToList(const QList& args) +{ + Tcl_Obj** objArray = new Tcl_Obj*[args.size()]; + + int i = 0; + for (const QVariant& arg : args) + objArray[i++] = variantToTclObj(arg); + + Tcl_Obj* obj = Tcl_NewListObj(args.size(), objArray); + delete[] objArray; + + return obj; +} + +QVariant ScriptingTcl::tclObjToVariant(Tcl_Obj* obj) +{ + static const QStringList typeLiterals = {"boolean", "booleanString", "double", "int", "wideInt", "bignum", "bytearray", "string", "list", "dict"}; + + TclDataType type = TclDataType::UNKNOWN; + if (obj->typePtr) + { + int typeIdx = typeLiterals.indexOf(obj->typePtr->name); + if (typeIdx > -1) + type = static_cast(typeIdx); + } + + QVariant result; + bool ok = true; + switch (type) + { + case TclDataType::Boolean: + case TclDataType::BooleanString: + { + int b; + if (Tcl_GetBooleanFromObj(nullptr, obj, &b) == TCL_OK) + result = (bool)b; + else + ok = false; + + break; + } + case TclDataType::Double: + { + double d; + if (Tcl_GetDoubleFromObj(nullptr, obj, &d) == TCL_OK) + result = d; + else + ok = false; + + break; + } + case TclDataType::Int: + { + int i; + if (Tcl_GetIntFromObj(nullptr, obj, &i) == TCL_OK) + result = i; + else + ok = false; + + break; + } + case TclDataType::WideInt: + { + Tcl_WideInt wideInt; + if (Tcl_GetWideIntFromObj(nullptr, obj, &wideInt) == TCL_OK) + result = (qint64)wideInt; + else + ok = false; + + break; + } + case TclDataType::Bytearray: + { + int lgt; + unsigned char* bytes = Tcl_GetByteArrayFromObj(obj, &lgt); + result = QByteArray::fromRawData(reinterpret_cast(bytes), lgt); + break; + } + case TclDataType::List: + { + QList list; + int objc; + Tcl_Obj** objv = nullptr; + Tcl_ListObjGetElements(nullptr, obj, &objc, &objv); + for (int i = 0; i < objc; i++) + list << tclObjToVariant(objv[i]); + + result = list; + break; + } + case TclDataType::Dict: + { + Tcl_DictSearch search; + Tcl_Obj* key = nullptr; + Tcl_Obj* value = nullptr; + QString keyStr; + QVariant valueVariant; + int done; + QHash hash; + if (Tcl_DictObjFirst(nullptr, obj, &search, &key, &value, &done) == TCL_OK) + { + for (; !done ; Tcl_DictObjNext(&search, &key, &value, &done)) + { + keyStr = QString::fromUtf8(Tcl_GetStringFromObj(key, nullptr)); + valueVariant = tclObjToVariant(value); + hash[keyStr] = valueVariant; + } + Tcl_DictObjDone(&search); + } + result = hash; + } + case TclDataType::Bignum: + case TclDataType::String: + case TclDataType::UNKNOWN: + default: + result = tclObjToString(obj); + break; + } + + if (!ok) + result = tclObjToString(obj); + + return result; +} + +QString ScriptingTcl::tclObjToString(Tcl_Obj* obj) +{ + return QString::fromUtf8(Tcl_GetStringFromObj(obj, nullptr)); +} + +Tcl_Obj* ScriptingTcl::variantToTclObj(const QVariant& value) +{ + Tcl_Obj* obj = nullptr; + switch (value.type()) + { + case QVariant::Bool: + obj = Tcl_NewBooleanObj(value.toBool()); + break; + case QVariant::Int: + case QVariant::UInt: + obj = Tcl_NewIntObj(value.toInt()); + break; + case QVariant::LongLong: + case QVariant::ULongLong: + obj = Tcl_NewWideIntObj((Tcl_WideInt)value.toLongLong()); + break; + case QVariant::Double: + obj = Tcl_NewDoubleObj(value.toDouble()); + break; + case QVariant::ByteArray: + { + QByteArray bytes = value.toByteArray(); + unsigned char* ubytes = reinterpret_cast(bytes.data()); + obj = Tcl_NewByteArrayObj(ubytes, bytes.size()); + break; + } + case QVariant::List: + { + QList list = value.toList(); + int listSize = list.size(); + Tcl_Obj** objList = new Tcl_Obj*[listSize]; + for (int i = 0; i < listSize; ++i) + objList[i] = variantToTclObj(list[i]); + + obj = Tcl_NewListObj(listSize, objList); + delete[] objList; + break; + } + case QVariant::StringList: + { + QStringList list = value.toStringList(); + int listSize = list.size(); + Tcl_Obj** objList = new Tcl_Obj*[listSize]; + for (int i = 0; i < listSize; ++i) + objList[i] = stringToTclObj(list[i]); + + obj = Tcl_NewListObj(listSize, objList); + delete[] objList; + break; + } + case QVariant::Hash: + { + QHash hash = value.toHash(); + obj = Tcl_NewDictObj(); + QHashIterator it(hash); + while (it.hasNext()) + { + it.next(); + Tcl_DictObjPut(nullptr, obj, variantToTclObj(it.key()), variantToTclObj(it.value())); + } + break; + } + case QVariant::Map: + { + QMap map = value.toMap(); + obj = Tcl_NewDictObj(); + QMapIterator it(map); + while (it.hasNext()) + { + it.next(); + Tcl_DictObjPut(nullptr, obj, variantToTclObj(it.key()), variantToTclObj(it.value())); + } + break; + } + case QVariant::String: + default: + obj = stringToTclObj(value.toString()); + break; + } + + if (!obj) + obj = stringToTclObj(value.toString()); + + return obj; +} + +Tcl_Obj* ScriptingTcl::stringToTclObj(const QString& value) +{ + return Tcl_NewStringObj(value.toUtf8().constData(), -1); +} + +int ScriptingTcl::dbCommand(ClientData clientData, Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]) +{ + ContextTcl* ctx = reinterpret_cast(clientData); + + Tcl_Obj* result = nullptr; + if (!ctx->db) + { + result = Tcl_NewStringObj(tr("No database available in current context, while called Tcl's 'db' command.").toUtf8().constData(), -1); + Tcl_SetObjResult(interp, result); + return TCL_ERROR; + } + + if (strcmp(Tcl_GetStringFromObj(objv[1], nullptr), "eval") == 0) + { + if (objc == 3) + return dbEval(ctx, interp, objv); + else if (objc == 5) { + return dbEvalRowByRow(ctx, interp, objv); + } + } + else if (strcmp(Tcl_GetStringFromObj(objv[1], nullptr), "rows") == 0 && objc == 3) + { + return dbEvalDeepResults(ctx, interp, objv); + } + else if (strcmp(Tcl_GetStringFromObj(objv[1], nullptr), "onecolumn") == 0 && objc == 3) + { + return dbEvalOneColumn(ctx, interp, objv); + } + + result = Tcl_NewStringObj(tr("Invalid 'db' command sytax. Should be: db eval sql").toUtf8().constData(), -1); + Tcl_SetObjResult(interp, result); + return TCL_ERROR; +} + +int ScriptingTcl::dbEval(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]) +{ + SqlQueryPtr execResults = dbCommonEval(ctx, interp, objv); + if (execResults->isError()) + return TCL_ERROR; + + Tcl_Obj* result = nullptr; + QList cells; + SqlResultsRowPtr row; + while (execResults->hasNext()) + { + row = execResults->next(); + cells += row->valueList(); + } + result = variantToTclObj(cells); + + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +int ScriptingTcl::dbEvalRowByRow(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]) +{ + SqlQueryPtr execResults = dbCommonEval(ctx, interp, objv); + if (execResults->isError()) + return TCL_ERROR; + + Tcl_Obj* code = objv[4]; + QString arrayName = tclObjToString(objv[3]); + const char* arrayCharName = arrayName.toUtf8().constData(); + SqlResultsRowPtr row; + int resCode = TCL_OK; + QHash valueMap; + while (execResults->hasNext()) + { + row = execResults->next(); + + Tcl_UnsetVar2(interp, arrayCharName, nullptr, 0); + valueMap = row->valueMap(); + valueMap["*"] = QStringList(valueMap.keys()); + if (setArrayVariable(interp, arrayName, valueMap) != TCL_OK) + return TCL_ERROR; + + resCode = Tcl_EvalObjEx(interp, code, 0); + + if (resCode == TCL_ERROR) + return TCL_ERROR; + else if (resCode == TCL_BREAK) + break; + else if (resCode == TCL_RETURN) + return TCL_RETURN; + } + + return TCL_OK; +} + +int ScriptingTcl::dbEvalDeepResults(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]) +{ + SqlQueryPtr execResults = dbCommonEval(ctx, interp, objv); + if (execResults->isError()) + return TCL_ERROR; + + Tcl_Obj* result = nullptr; + QList rows; + SqlResultsRowPtr row; + while (execResults->hasNext()) + { + row = execResults->next(); + rows << QVariant(row->valueList()); + } + result = variantToTclObj(rows); + + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +int ScriptingTcl::dbEvalOneColumn(ScriptingTcl::ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]) +{ + SqlQueryPtr execResults = dbCommonEval(ctx, interp, objv); + if (execResults->isError()) + return TCL_ERROR; + + Tcl_Obj* result = nullptr; + QVariant resultValue; + if (execResults->hasNext()) + resultValue = execResults->getSingleCell(); + + result = variantToTclObj(resultValue); + + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +SqlQueryPtr ScriptingTcl::dbCommonEval(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]) +{ + Db::Flags flags; + if (!ctx->useDbLocking) + flags |= Db::Flag::NO_LOCK; + + Tcl_Obj* result = nullptr; + QString sql = QString::fromUtf8(Tcl_GetStringFromObj(objv[2], nullptr)); + + TokenList bindTokens = Lexer::tokenize(sql, ctx->db->getDialect()).filter(Token::BIND_PARAM); + QString bindVarName; + QHash queryArgs; + for (const TokenPtr& token : bindTokens) + { + bindVarName = getBindTokenName(token); + if (bindVarName == "?") + continue; + + queryArgs[token->value] = getVariable(interp, bindVarName); + } + + SqlQueryPtr execResults = ctx->db->exec(sql, queryArgs, flags); + if (execResults->isError()) + { + result = Tcl_NewStringObj(tr("Error from Tcl's' 'db' command: %1").arg(execResults->getErrorText()).toUtf8().constData(), -1); + Tcl_SetObjResult(interp, result); + } + return execResults; +} + +int ScriptingTcl::setArrayVariable(Tcl_Interp* interp, const QString& arrayName, const QHash& hash) +{ + Tcl_Obj* varName = Tcl_NewStringObj(arrayName.toUtf8().constData(), -1); + Tcl_IncrRefCount(varName); + + Tcl_Obj* key = nullptr; + Tcl_Obj* value = nullptr; + Tcl_Obj* res = nullptr; + + QHashIterator it(hash); + while (it.hasNext()) + { + it.next(); + key = variantToTclObj(it.key()); + value = variantToTclObj(it.value()); + + Tcl_IncrRefCount(key); + Tcl_IncrRefCount(value); + + res = Tcl_ObjSetVar2(interp, varName, key, value, 0); + + Tcl_DecrRefCount(key); + Tcl_DecrRefCount(value); + + if (!res) + return TCL_ERROR; + } + return TCL_OK; +} + +void ScriptingTcl::setVariable(Tcl_Interp* interp, const QString& name, const QVariant& value) +{ + Tcl_Obj* varName = Tcl_NewStringObj(name.toUtf8().constData(), -1); + Tcl_IncrRefCount(varName); + Tcl_Obj* tclObjValue = variantToTclObj(value); + Tcl_IncrRefCount(tclObjValue); + Tcl_ObjSetVar2(interp, varName, nullptr, tclObjValue, 0); + Tcl_DecrRefCount(tclObjValue); + Tcl_DecrRefCount(varName); +} + +QVariant ScriptingTcl::getVariable(Tcl_Interp* interp, const QString& name) +{ + Tcl_Obj* varName = Tcl_NewStringObj(name.toUtf8().constData(), -1); + Tcl_IncrRefCount(varName); + Tcl_Obj* obj = Tcl_ObjGetVar2(interp, varName, nullptr, 0); + if (!obj) + return QVariant(); + + Tcl_IncrRefCount(obj); + QVariant val = tclObjToVariant(obj); + Tcl_DecrRefCount(varName); + Tcl_DecrRefCount(obj); + return val; +} + +ScriptingTcl::ScriptObject::ScriptObject(const QString& code) +{ + QByteArray utf8Bytes = code.toUtf8(); + obj = Tcl_NewStringObj(utf8Bytes.constData(), utf8Bytes.size()); + Tcl_IncrRefCount(obj); +} + +ScriptingTcl::ScriptObject::~ScriptObject() +{ + Tcl_DecrRefCount(obj); +} + +Tcl_Obj* ScriptingTcl::ScriptObject::getTclObj() +{ + return obj; +} + +ScriptingTcl::ContextTcl::ContextTcl() +{ + scriptCache.setMaxCost(cacheSize); + interp = Tcl_CreateInterp(); + init(); +} + +ScriptingTcl::ContextTcl::~ContextTcl() +{ + Tcl_DeleteInterp(interp); +} + +void ScriptingTcl::ContextTcl::reset() +{ + Tcl_DeleteInterp(interp); + interp = Tcl_CreateInterp(); + error = QString(); + init(); +} + +void ScriptingTcl::ContextTcl::init() +{ + Tcl_CreateObjCommand(interp, "db", ScriptingTcl::dbCommand, reinterpret_cast(this), nullptr); +} diff --git a/Plugins/ScriptingTcl/scriptingtcl.h b/Plugins/ScriptingTcl/scriptingtcl.h new file mode 100644 index 0000000..8fe44d5 --- /dev/null +++ b/Plugins/ScriptingTcl/scriptingtcl.h @@ -0,0 +1,111 @@ +#ifndef SCRIPTINGTCL_H +#define SCRIPTINGTCL_H + +#include "scriptingtcl_global.h" +#include "plugins/genericplugin.h" +#include "plugins/scriptingplugin.h" +#include "db/sqlquery.h" +#include +#include + +class QMutex; +struct Tcl_Interp; +struct Tcl_Obj; + +class SCRIPTINGTCLSHARED_EXPORT ScriptingTcl : public GenericPlugin, public DbAwareScriptingPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("scriptingtcl.json") + + public: + ScriptingTcl(); + ~ScriptingTcl(); + + bool init(); + void deinit(); + QString getLanguage() const; + Context* createContext(); + void releaseContext(Context* context); + void resetContext(Context* context); + void setVariable(Context* context, const QString& name, const QVariant& value); + QVariant getVariable(Context* context, const QString& name); + bool hasError(Context* context) const; + QString getErrorMessage(Context* context) const; + QString getIconPath() const; + QVariant evaluate(Context* context, const QString& code, const QList& args, Db* db, bool locking = false); + QVariant evaluate(const QString& code, const QList& args, Db* db, bool locking = false, QString* errorMessage = nullptr); + + private: + class ScriptObject + { + public: + ScriptObject(const QString& code); + ~ScriptObject(); + + Tcl_Obj* getTclObj(); + + private: + Tcl_Obj* obj = nullptr; + }; + + class ContextTcl : public ScriptingPlugin::Context + { + public: + ContextTcl(); + ~ContextTcl(); + + void reset(); + + Tcl_Interp* interp = nullptr; + QCache scriptCache; + QString error; + Db* db = nullptr; + bool useDbLocking = false; + + private: + void init(); + }; + + enum class TclDataType + { + Boolean, + BooleanString, + Double, + Int, + WideInt, + Bignum, + Bytearray, + String, + List, + Dict, + UNKNOWN + }; + + ContextTcl* getContext(ScriptingPlugin::Context* context) const; + QVariant compileAndEval(ContextTcl* ctx, const QString& code, Db* db, bool locking); + QVariant extractResult(ContextTcl* ctx); + void setArgs(ContextTcl* ctx, const QList& args); + + static Tcl_Obj* argsToList(const QList& args); + static QVariant tclObjToVariant(Tcl_Obj* obj); + static QString tclObjToString(Tcl_Obj* obj); + static Tcl_Obj* variantToTclObj(const QVariant& value); + static Tcl_Obj* stringToTclObj(const QString& value); + static int dbCommand(ClientData clientData, Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]); + static int dbEval(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]); + static int dbEvalRowByRow(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]); + static int dbEvalDeepResults(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]); + static int dbEvalOneColumn(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]); + static SqlQueryPtr dbCommonEval(ContextTcl* ctx, Tcl_Interp* interp, Tcl_Obj* const objv[]); + static int setArrayVariable(Tcl_Interp* interp, const QString& arrayName, const QHash& hash); + static void setVariable(Tcl_Interp* interp, const QString& name, const QVariant& value); + static QVariant getVariable(Tcl_Interp* interp, const QString& name); + + static const constexpr int cacheSize = 5; + + ContextTcl* mainContext = nullptr; + QList contexts; + QMutex* mainInterpMutex = nullptr; +}; + +#endif // SCRIPTINGTCL_H diff --git a/Plugins/ScriptingTcl/scriptingtcl.json b/Plugins/ScriptingTcl/scriptingtcl.json new file mode 100644 index 0000000..108d7c0 --- /dev/null +++ b/Plugins/ScriptingTcl/scriptingtcl.json @@ -0,0 +1,7 @@ +{ + "type": "ScriptingPlugin", + "title": "Tcl scripting", + "description": "Provides Tcl scripting language support for SQLiteStudio.", + "version": 10001, + "author": "SalSoft" +} diff --git a/Plugins/ScriptingTcl/scriptingtcl.png b/Plugins/ScriptingTcl/scriptingtcl.png new file mode 100644 index 0000000..fa4c6ad Binary files /dev/null and b/Plugins/ScriptingTcl/scriptingtcl.png differ diff --git a/Plugins/ScriptingTcl/scriptingtcl.qrc b/Plugins/ScriptingTcl/scriptingtcl.qrc new file mode 100644 index 0000000..8a0d047 --- /dev/null +++ b/Plugins/ScriptingTcl/scriptingtcl.qrc @@ -0,0 +1,5 @@ + + + scriptingtcl.png + + diff --git a/Plugins/ScriptingTcl/scriptingtcl_global.h b/Plugins/ScriptingTcl/scriptingtcl_global.h new file mode 100644 index 0000000..006bcc0 --- /dev/null +++ b/Plugins/ScriptingTcl/scriptingtcl_global.h @@ -0,0 +1,12 @@ +#ifndef SCRIPTINGTCL_GLOBAL_H +#define SCRIPTINGTCL_GLOBAL_H + +#include + +#if defined(SCRIPTINGTCL_LIBRARY) +# define SCRIPTINGTCLSHARED_EXPORT Q_DECL_EXPORT +#else +# define SCRIPTINGTCLSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // SCRIPTINGTCL_GLOBAL_H diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro new file mode 100644 index 0000000..ae68b60 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro @@ -0,0 +1,100 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-09-11T10:57:25 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = SqlEnterpriseFormatter +TEMPLATE = lib + +DEFINES += SQLENTERPRISEFORMATTER_LIBRARY + +SOURCES += sqlenterpriseformatter.cpp \ + formatstatement.cpp \ + formatselect.cpp \ + formatexpr.cpp \ + formatlimit.cpp \ + formatwith.cpp \ + formatraise.cpp \ + formatcreatetable.cpp \ + formatforeignkey.cpp \ + formatcolumntype.cpp \ + formatindexedcolumn.cpp \ + formatinsert.cpp \ + formatempty.cpp \ + formataltertable.cpp \ + formatanalyze.cpp \ + formatattach.cpp \ + formatbegintrans.cpp \ + formatcommittrans.cpp \ + formatcopy.cpp \ + formatcreateindex.cpp \ + formatcreatetrigger.cpp \ + formatdelete.cpp \ + formatupdate.cpp \ + formatcreateview.cpp \ + formatcreatevirtualtable.cpp \ + formatdetach.cpp \ + formatdropindex.cpp \ + formatdroptable.cpp \ + formatdroptrigger.cpp \ + formatdropview.cpp \ + formatpragma.cpp \ + formatreindex.cpp \ + formatrelease.cpp \ + formatrollback.cpp \ + formatsavepoint.cpp \ + formatvacuum.cpp \ + formatorderby.cpp + +HEADERS += sqlenterpriseformatter.h\ + sqlenterpriseformatter_global.h \ + formatstatement.h \ + formatselect.h \ + formatexpr.h \ + formatlimit.h \ + formatwith.h \ + formatraise.h \ + formatcreatetable.h \ + formatforeignkey.h \ + formatcolumntype.h \ + formatindexedcolumn.h \ + formatinsert.h \ + formatempty.h \ + formataltertable.h \ + formatanalyze.h \ + formatattach.h \ + formatbegintrans.h \ + formatcommittrans.h \ + formatcopy.h \ + formatcreateindex.h \ + formatcreatetrigger.h \ + formatdelete.h \ + formatupdate.h \ + formatcreateview.h \ + formatcreatevirtualtable.h \ + formatdetach.h \ + formatdropindex.h \ + formatdroptable.h \ + formatdroptrigger.h \ + formatdropview.h \ + formatpragma.h \ + formatreindex.h \ + formatrelease.h \ + formatrollback.h \ + formatsavepoint.h \ + formatvacuum.h \ + formatorderby.h + +OTHER_FILES += \ + sqlenterpriseformatter.json + +FORMS += \ + sqlenterpriseformatter.ui + +RESOURCES += \ + sqlenterpriseformatter.qrc diff --git a/Plugins/SqlEnterpriseFormatter/formataltertable.cpp b/Plugins/SqlEnterpriseFormatter/formataltertable.cpp new file mode 100644 index 0000000..d562e1b --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formataltertable.cpp @@ -0,0 +1,31 @@ +#include "formataltertable.h" +#include "parser/ast/sqlitealtertable.h" + +FormatAlterTable::FormatAlterTable(SqliteAlterTable* alterTable) : + alterTable(alterTable) +{ +} + +void FormatAlterTable::formatInternal() +{ + withKeyword("ALTER").withKeyword("TABLE"); + + if (!alterTable->database.isNull()) + withId(alterTable->database).withIdDot(); + + withId(alterTable->table); + + if (alterTable->newColumn) + { + withKeyword("ADD"); + if (alterTable->columnKw) + withKeyword("COLUMN"); + + withStatement(alterTable->newColumn); + } + else if (!alterTable->newName.isNull()) + { + withKeyword("RENAME").withKeyword("TO").withId(alterTable->newName); + } + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formataltertable.h b/Plugins/SqlEnterpriseFormatter/formataltertable.h new file mode 100644 index 0000000..a041c9c --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formataltertable.h @@ -0,0 +1,20 @@ +#ifndef FORMATALTERTABLE_H +#define FORMATALTERTABLE_H + +#include "formatstatement.h" + +class SqliteAlterTable; + +class FormatAlterTable : public FormatStatement +{ + public: + FormatAlterTable(SqliteAlterTable* alterTable); + + protected: + void formatInternal(); + + private: + SqliteAlterTable* alterTable = nullptr; +}; + +#endif // FORMATALTERTABLE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatanalyze.cpp b/Plugins/SqlEnterpriseFormatter/formatanalyze.cpp new file mode 100644 index 0000000..1b7a939 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatanalyze.cpp @@ -0,0 +1,18 @@ +#include "formatanalyze.h" +#include "parser/ast/sqliteanalyze.h" + +FormatAnalyze::FormatAnalyze(SqliteAnalyze* analyze) : + analyze(analyze) +{ +} + +void FormatAnalyze::formatInternal() +{ + withKeyword("ANALYZE"); + + if (!analyze->database.isNull()) + withId(analyze->database).withIdDot(); + + withId(analyze->table).withSemicolon(); + +} diff --git a/Plugins/SqlEnterpriseFormatter/formatanalyze.h b/Plugins/SqlEnterpriseFormatter/formatanalyze.h new file mode 100644 index 0000000..f9e8aee --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatanalyze.h @@ -0,0 +1,20 @@ +#ifndef FORMATANALYZE_H +#define FORMATANALYZE_H + +#include "formatstatement.h" + +class SqliteAnalyze; + +class FormatAnalyze : public FormatStatement +{ + public: + FormatAnalyze(SqliteAnalyze* analyze); + + protected: + void formatInternal(); + + private: + SqliteAnalyze* analyze = nullptr; +}; + +#endif // FORMATANALYZE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatattach.cpp b/Plugins/SqlEnterpriseFormatter/formatattach.cpp new file mode 100644 index 0000000..e969495 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatattach.cpp @@ -0,0 +1,22 @@ +#include "formatattach.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqliteexpr.h" + +FormatAttach::FormatAttach(SqliteAttach* att) : + att(att) +{ +} + +void FormatAttach::formatInternal() +{ + withKeyword("ATTACH"); + + if (att->databaseKw) + withKeyword("DATABASE"); + + withStatement(att->databaseUrl).withKeyword("AS").withStatement(att->name); + if (att->key) + withKeyword("KEY").withStatement(att->key); + + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatattach.h b/Plugins/SqlEnterpriseFormatter/formatattach.h new file mode 100644 index 0000000..cfd06f2 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatattach.h @@ -0,0 +1,20 @@ +#ifndef FORMATATTACH_H +#define FORMATATTACH_H + +#include "formatstatement.h" + +class SqliteAttach; + +class FormatAttach : public FormatStatement +{ + public: + FormatAttach(SqliteAttach* att); + + protected: + void formatInternal(); + + private: + SqliteAttach* att = nullptr; +}; + +#endif // FORMATATTACH_H diff --git a/Plugins/SqlEnterpriseFormatter/formatbegintrans.cpp b/Plugins/SqlEnterpriseFormatter/formatbegintrans.cpp new file mode 100644 index 0000000..5a70d61 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatbegintrans.cpp @@ -0,0 +1,24 @@ +#include "formatbegintrans.h" +#include "parser/ast/sqlitebegintrans.h" + +FormatBeginTrans::FormatBeginTrans(SqliteBeginTrans* bt) : + bt(bt) +{ +} + +void FormatBeginTrans::formatInternal() +{ + withKeyword("BEGIN"); + + if (bt->type != SqliteBeginTrans::Type::null) + withKeyword(SqliteBeginTrans::typeToString(bt->type)); + + if (bt->transactionKw) + { + withKeyword("TRANSACTION"); + if (!bt->name.isNull()) + withId(bt->name); + } + + withConflict(bt->onConflict).withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatbegintrans.h b/Plugins/SqlEnterpriseFormatter/formatbegintrans.h new file mode 100644 index 0000000..10d6dc1 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatbegintrans.h @@ -0,0 +1,20 @@ +#ifndef FORMATBEGINTRANS_H +#define FORMATBEGINTRANS_H + +#include "formatstatement.h" + +class SqliteBeginTrans; + +class FormatBeginTrans : public FormatStatement +{ + public: + FormatBeginTrans(SqliteBeginTrans* bt); + + protected: + void formatInternal(); + + private: + SqliteBeginTrans* bt = nullptr; +}; + +#endif // FORMATBEGINTRANS_H diff --git a/Plugins/SqlEnterpriseFormatter/formatcolumntype.cpp b/Plugins/SqlEnterpriseFormatter/formatcolumntype.cpp new file mode 100644 index 0000000..1ff4c1d --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcolumntype.cpp @@ -0,0 +1,43 @@ +#include "formatcolumntype.h" +#include "parser/ast/sqlitecolumntype.h" +#include "sqlenterpriseformatter.h" + +FormatColumnType::FormatColumnType(SqliteColumnType* colType) : + colType(colType) +{ +} + +void FormatColumnType::formatInternal() +{ + if (colType->name.isEmpty()) + return; + + withId(cfg->SqlEnterpriseFormatter.UppercaseDataTypes.get() ? colType->name.toUpper() : colType->name.toLower()); + + if (!colType->scale.isNull()) + { + withParExprLeft(); + if (colType->scale.userType() == QVariant::Int) + withInteger(colType->scale.toInt()); + else if (colType->scale.userType() == QVariant::LongLong) + withInteger(colType->scale.toLongLong()); + else if (colType->scale.userType() == QVariant::Double) + withFloat(colType->scale.toDouble()); + else + withId(colType->scale.toString()); + + if (!colType->precision.isNull()) + { + withCommaOper(); + if (colType->precision.userType() == QVariant::Int) + withInteger(colType->precision.toInt()); + else if (colType->precision.userType() == QVariant::LongLong) + withInteger(colType->precision.toLongLong()); + else if (colType->precision.userType() == QVariant::Double) + withFloat(colType->precision.toDouble()); + else + withId(colType->precision.toString()); + } + withParExprRight(); + } +} diff --git a/Plugins/SqlEnterpriseFormatter/formatcolumntype.h b/Plugins/SqlEnterpriseFormatter/formatcolumntype.h new file mode 100644 index 0000000..6404aa5 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcolumntype.h @@ -0,0 +1,20 @@ +#ifndef FORMATCOLUMNTYPE_H +#define FORMATCOLUMNTYPE_H + +#include "formatstatement.h" + +class SqliteColumnType; + +class FormatColumnType : public FormatStatement +{ + public: + FormatColumnType(SqliteColumnType* colType); + + protected: + void formatInternal(); + + private: + SqliteColumnType* colType = nullptr; +}; + +#endif // FORMATCOLUMNTYPE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatcommittrans.cpp b/Plugins/SqlEnterpriseFormatter/formatcommittrans.cpp new file mode 100644 index 0000000..7fdafa3 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcommittrans.cpp @@ -0,0 +1,24 @@ +#include "formatcommittrans.h" +#include "parser/ast/sqlitecommittrans.h" + +FormatCommitTrans::FormatCommitTrans(SqliteCommitTrans* ct) : + ct(ct) +{ +} + +void FormatCommitTrans::formatInternal() +{ + if (ct->endKw) + withKeyword("END"); + else + withKeyword("COMMIT"); + + if (ct->transactionKw) + { + withKeyword("TRANSACTION"); + if (!ct->name.isNull()) + withId(ct->name); + } + + withOperator(";"); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatcommittrans.h b/Plugins/SqlEnterpriseFormatter/formatcommittrans.h new file mode 100644 index 0000000..5de2a88 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcommittrans.h @@ -0,0 +1,20 @@ +#ifndef FORMATCOMMITTRANS_H +#define FORMATCOMMITTRANS_H + +#include "formatstatement.h" + +class SqliteCommitTrans; + +class FormatCommitTrans : public FormatStatement +{ + public: + FormatCommitTrans(SqliteCommitTrans* ct); + + protected: + void formatInternal(); + + private: + SqliteCommitTrans* ct = nullptr; +}; + +#endif // FORMATCOMMITTRANS_H diff --git a/Plugins/SqlEnterpriseFormatter/formatcopy.cpp b/Plugins/SqlEnterpriseFormatter/formatcopy.cpp new file mode 100644 index 0000000..fa07d48 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcopy.cpp @@ -0,0 +1,24 @@ +#include "formatcopy.h" +#include "parser/ast/sqlitecopy.h" + +FormatCopy::FormatCopy(SqliteCopy* copy) : + copy(copy) +{ +} + +void FormatCopy::formatInternal() +{ + withKeyword("COPY"); + if (copy->onConflict != SqliteConflictAlgo::null) + withKeyword("OR").withKeyword(sqliteConflictAlgo(copy->onConflict)); + + if (!copy->database.isNull()) + withId(copy->database); + + withId(copy->table).withKeyword("FROM").withString(copy->file); + + if (!copy->delimiter.isNull()) + withKeyword("USING").withKeyword("DELIMITERS").withString(copy->delimiter); + + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatcopy.h b/Plugins/SqlEnterpriseFormatter/formatcopy.h new file mode 100644 index 0000000..c0125e9 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcopy.h @@ -0,0 +1,20 @@ +#ifndef FORMATCOPY_H +#define FORMATCOPY_H + +#include "formatstatement.h" + +class SqliteCopy; + +class FormatCopy : public FormatStatement +{ + public: + FormatCopy(SqliteCopy* copy); + + protected: + void formatInternal(); + + private: + SqliteCopy* copy = nullptr; +}; + +#endif // FORMATCOPY_H diff --git a/Plugins/SqlEnterpriseFormatter/formatcreateindex.cpp b/Plugins/SqlEnterpriseFormatter/formatcreateindex.cpp new file mode 100644 index 0000000..cc8f3f6 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreateindex.cpp @@ -0,0 +1,42 @@ +#include "formatcreateindex.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqliteindexedcolumn.h" + +FormatCreateIndex::FormatCreateIndex(SqliteCreateIndex* createIndex) : + createIndex(createIndex) +{ +} + +void FormatCreateIndex::formatInternal() +{ + withKeyword("CREATE"); + if (createIndex->uniqueKw) + withKeyword("UNIQUE"); + + withKeyword("INDEX"); + + if (createIndex->ifNotExistsKw) + withKeyword("IF").withKeyword("NOT").withKeyword("EXISTS"); + + if (dialect == Dialect::Sqlite2) + { + withId(createIndex->index).withKeyword("ON"); + + if (!createIndex->database.isNull()) + withId(createIndex->database).withIdDot(); + + withId(createIndex->table).withParDefLeft().withStatementList(createIndex->indexedColumns).withParDefRight().withConflict(createIndex->onConflict); + } + else + { + if (!createIndex->database.isNull()) + withId(createIndex->database).withIdDot(); + + withId(createIndex->index).withKeyword("ON").withId(createIndex->table).withParDefLeft().withStatementList(createIndex->indexedColumns).withParDefRight(); + + if (createIndex->where) + withKeyword("WHERE").withStatement(createIndex->where); + } + + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatcreateindex.h b/Plugins/SqlEnterpriseFormatter/formatcreateindex.h new file mode 100644 index 0000000..1f1b109 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreateindex.h @@ -0,0 +1,20 @@ +#ifndef FORMATCREATEINDEX_H +#define FORMATCREATEINDEX_H + +#include "formatstatement.h" + +class SqliteCreateIndex; + +class FormatCreateIndex : public FormatStatement +{ + public: + FormatCreateIndex(SqliteCreateIndex* createIndex); + + protected: + void formatInternal(); + + private: + SqliteCreateIndex* createIndex = nullptr; +}; + +#endif // FORMATCREATEINDEX_H diff --git a/Plugins/SqlEnterpriseFormatter/formatcreatetable.cpp b/Plugins/SqlEnterpriseFormatter/formatcreatetable.cpp new file mode 100644 index 0000000..d37239a --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreatetable.cpp @@ -0,0 +1,224 @@ +#include "formatcolumntype.h" +#include "formatcreatetable.h" +#include "sqlenterpriseformatter.h" + +FormatCreateTable::FormatCreateTable(SqliteCreateTable* createTable) : + createTable(createTable) +{ +} + +void FormatCreateTable::formatInternal() +{ + withKeyword("CREATE"); + if (createTable->tempKw) + withKeyword("TEMP"); + else if (createTable->temporaryKw) + withKeyword("TEMPORARY"); + + withKeyword("TABLE"); + if (createTable->ifNotExistsKw) + withKeyword("IF").withKeyword("NOT").withKeyword("EXISTS"); + + if (dialect == Dialect::Sqlite3 && !createTable->database.isNull()) + withId(createTable->database).withIdDot(); + + withId(createTable->table); + + if (createTable->select) + withKeyword("AS").withStatement(createTable->select); + else + { + withParDefLeft(); + formatColumns(createTable->columns); + if (createTable->constraints.size() > 0) + withListComma().withStatementList(createTable->constraints); + + withParDefRight(); + + if (!createTable->withOutRowId.isNull()) + withKeyword("WITHOUT").withId("ROWID"); + } + + withSemicolon(); +} + +void FormatCreateTable::formatColumns(const QList& columns) +{ + int maxColNameIndent = 0; + int maxColTypeIndent = 0; + FormatColumnType* formatColType = nullptr; + foreach (SqliteCreateTable::Column* stmt, columns) + { + maxColNameIndent = qMax(getColNameLength(stmt->name), maxColNameIndent); + + if (stmt->type) + { + + formatColType = getFormatStatement(stmt->type); + maxColTypeIndent = qMax(formatColType->format().trimmed().length(), maxColTypeIndent); + delete formatColType; + } + } + + if (columns.size() > 1) + { + maxColNameIndent++; // for a single whitespace to line up with other columns + maxColTypeIndent++; // the same for constraints + } + + withStatementList(columns, "columns", ListSeparator::COMMA, [maxColNameIndent, maxColTypeIndent](FormatStatement* formatStmt) + { + FormatCreateTableColumn* colStmt = dynamic_cast(formatStmt); + if (colStmt) + { + colStmt->setColNameIndent(maxColNameIndent); + colStmt->setColTypeIndent(maxColTypeIndent); + } + }); +} + +int FormatCreateTable::getColNameLength(const QString& name) +{ + if (cfg->SqlEnterpriseFormatter.AlwaysUseNameWrapping.get()) + return wrapObjName(name, dialect, wrapper).length(); + else + return wrapObjIfNeeded(name, dialect, wrapper).length(); +} + +FormatCreateTableColumn::FormatCreateTableColumn(SqliteCreateTable::Column* column) : + column(column) +{ +} + +void FormatCreateTableColumn::setColNameIndent(int value) +{ + colNameIndent = value; +} + +void FormatCreateTableColumn::setColTypeIndent(int value) +{ + colTypeIndent = value; +} + +void FormatCreateTableColumn::formatInternal() +{ + ListSeparator sep = ListSeparator::NONE; + if (cfg->SqlEnterpriseFormatter.NlBetweenConstraints.get()) + sep = ListSeparator::NEW_LINE; + + withId(column->name).withIncrIndent(colNameIndent).withStatement(column->type).withIncrIndent(colTypeIndent) + .withStatementList(column->constraints, QString(), sep).withDecrIndent().withDecrIndent(); +} + + +FormatCreateTableColumnConstraint::FormatCreateTableColumnConstraint(SqliteCreateTable::Column::Constraint* constr) : + constr(constr) +{ +} + +void FormatCreateTableColumnConstraint::formatInternal() +{ + if (!constr->name.isNull()) + withKeyword("CONSTRAINT").withId(constr->name); + + switch (constr->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + { + withKeyword("PRIMARY").withKeyword("KEY").withSortOrder(constr->sortOrder).withConflict(constr->onConflict); + if (constr->autoincrKw) + withKeyword("AUTOINCREMENT"); + + break; + } + case SqliteCreateTable::Column::Constraint::NOT_NULL: + { + withKeyword("NOT").withKeyword("NULL").withConflict(constr->onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::UNIQUE: + { + withKeyword("UNIQUE").withConflict(constr->onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::CHECK: + { + withKeyword("CHECK").withParExprLeft().withStatement(constr->expr).withParExprRight().withConflict(constr->onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::DEFAULT: + { + withKeyword("DEFAULT"); + if (!constr->id.isNull()) + withId(constr->id); + else if (!constr->ctime.isNull()) + withKeyword(constr->ctime); + else if (constr->expr) + withParExprLeft().withStatement(constr->expr).withParExprRight(); + else if (constr->literalNull) + withKeyword("NULL"); + else + withLiteral(constr->literalValue); + + break; + } + case SqliteCreateTable::Column::Constraint::COLLATE: + { + withKeyword("COLLATE").withId(constr->collationName); + break; + } + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + { + withStatement(constr->foreignKey); + break; + } + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } +} + + +FormatCreateTableConstraint::FormatCreateTableConstraint(SqliteCreateTable::Constraint* constr) : + constr(constr) +{ +} + +void FormatCreateTableConstraint::formatInternal() +{ + if (!constr->name.isNull()) + withKeyword("CONSTRAINT").withId(constr->name); + + switch (constr->type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + { + withKeyword("PRIMARY").withKeyword("KEY").withParDefLeft().withStatementList(constr->indexedColumns).withParDefRight(); + + if (constr->autoincrKw) + withKeyword("AUTOINCREMENT"); + + withConflict(constr->onConflict); + break; + } + case SqliteCreateTable::Constraint::UNIQUE: + { + withKeyword("UNIQUE").withParDefLeft().withStatementList(constr->indexedColumns).withParDefRight().withConflict(constr->onConflict); + break; + } + case SqliteCreateTable::Constraint::CHECK: + { + withKeyword("CHECK").withParExprLeft().withStatement(constr->expr).withParExprRight().withConflict(constr->onConflict); + break; + } + case SqliteCreateTable::Constraint::FOREIGN_KEY: + { + withKeyword("FOREIGN").withKeyword("KEY").withParDefLeft().withStatementList(constr->indexedColumns) + .withParDefRight().withStatement(constr->foreignKey); + break; + } + case SqliteCreateTable::Constraint::NAME_ONLY: + break; + } +} diff --git a/Plugins/SqlEnterpriseFormatter/formatcreatetable.h b/Plugins/SqlEnterpriseFormatter/formatcreatetable.h new file mode 100644 index 0000000..8c1d720 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreatetable.h @@ -0,0 +1,63 @@ +#ifndef FORMATCREATETABLE_H +#define FORMATCREATETABLE_H + +#include "formatstatement.h" +#include "parser/ast/sqlitecreatetable.h" + +class FormatCreateTable : public FormatStatement +{ + public: + FormatCreateTable(SqliteCreateTable* createTable); + + protected: + void formatInternal(); + + private: + void formatColumns(const QList& columns); + int getColNameLength(const QString& name); + + SqliteCreateTable* createTable = nullptr; +}; + +class FormatCreateTableColumn : public FormatStatement +{ + public: + FormatCreateTableColumn(SqliteCreateTable::Column* column); + + void setColNameIndent(int value); + void setColTypeIndent(int value); + + protected: + void formatInternal(); + + private: + SqliteCreateTable::Column* column = nullptr; + int colNameIndent = 0; + int colTypeIndent = 0; +}; + +class FormatCreateTableColumnConstraint : public FormatStatement +{ + public: + FormatCreateTableColumnConstraint(SqliteCreateTable::Column::Constraint* constr); + + protected: + void formatInternal(); + + private: + SqliteCreateTable::Column::Constraint* constr = nullptr; +}; + +class FormatCreateTableConstraint : public FormatStatement +{ + public: + FormatCreateTableConstraint(SqliteCreateTable::Constraint* constr); + + protected: + void formatInternal(); + + private: + SqliteCreateTable::Constraint* constr = nullptr; +}; + +#endif // FORMATCREATETABLE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.cpp b/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.cpp new file mode 100644 index 0000000..d05d8cf --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.cpp @@ -0,0 +1,90 @@ +#include "formatcreatetrigger.h" +#include "parser/ast/sqliteexpr.h" + +FormatCreateTrigger::FormatCreateTrigger(SqliteCreateTrigger* createTrig) : + createTrig(createTrig) +{ +} + +void FormatCreateTrigger::formatInternal() +{ + withKeyword("CREATE"); + if (createTrig->tempKw) + withKeyword("TEMP"); + else if (createTrig->temporaryKw) + withKeyword("TEMPORARY"); + + withKeyword("TRIGGER"); + if (createTrig->ifNotExistsKw) + withKeyword("IF").withKeyword("NOT").withKeyword("EXISTS"); + + if (dialect == Dialect::Sqlite3 && !createTrig->database.isNull()) + withId(createTrig->database).withIdDot(); + + withId(createTrig->trigger); + switch (createTrig->eventTime) + { + case SqliteCreateTrigger::Time::BEFORE: + withKeyword("BEFORE"); + break; + case SqliteCreateTrigger::Time::AFTER: + withKeyword("AFTER"); + break; + case SqliteCreateTrigger::Time::INSTEAD_OF: + withKeyword("INSTEAD").withKeyword("OF"); + break; + case SqliteCreateTrigger::Time::null: + break; + } + + withStatement(createTrig->event).withKeyword("ON"); + if (dialect == Dialect::Sqlite2 && !createTrig->database.isNull()) + withId(createTrig->database).withIdDot(); + + withId(createTrig->table); + + switch (createTrig->scope) + { + case SqliteCreateTrigger::Scope::FOR_EACH_ROW: + withKeyword("FOR").withKeyword("EACH").withKeyword("ROW"); + break; + case SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT: + withKeyword("FOR").withKeyword("EACH").withKeyword("STATEMENT"); + break; + case SqliteCreateTrigger::Scope::null: + break; + } + + if (createTrig->precondition) + withKeyword("WHEN").withStatement(createTrig->precondition); + + withNewLine().withKeyword("BEGIN").withNewLine().withIncrIndent().withStatementList(createTrig->queries, QString(), ListSeparator::SEMICOLON).withSemicolon(); + withDecrIndent().withKeyword("END").withSemicolon(); +} + + +FormatCreateTriggerEvent::FormatCreateTriggerEvent(SqliteCreateTrigger::Event* ev) : + ev(ev) +{ +} + +void FormatCreateTriggerEvent::formatInternal() +{ + switch (ev->type) + { + case SqliteCreateTrigger::Event::INSERT: + withKeyword("INSERT"); + break; + case SqliteCreateTrigger::Event::UPDATE: + withKeyword("UPDATE"); + break; + case SqliteCreateTrigger::Event::DELETE: + withKeyword("DELETE"); + break; + case SqliteCreateTrigger::Event::UPDATE_OF: + withKeyword("UPDATE").withKeyword("OF").withIdList(ev->columnNames); + break; + case SqliteCreateTrigger::Event::null: + break; + } +} diff --git a/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.h b/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.h new file mode 100644 index 0000000..795c2c7 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreatetrigger.h @@ -0,0 +1,31 @@ +#ifndef FORMATCREATETRIGGER_H +#define FORMATCREATETRIGGER_H + +#include "formatstatement.h" +#include "parser/ast/sqlitecreatetrigger.h" + +class FormatCreateTrigger : public FormatStatement +{ + public: + FormatCreateTrigger(SqliteCreateTrigger* createTrig); + + protected: + void formatInternal(); + + private: + SqliteCreateTrigger* createTrig = nullptr; +}; + +class FormatCreateTriggerEvent : public FormatStatement +{ + public: + FormatCreateTriggerEvent(SqliteCreateTrigger::Event* ev); + + protected: + void formatInternal(); + + private: + SqliteCreateTrigger::Event* ev = nullptr; +}; + +#endif // FORMATCREATETRIGGER_H diff --git a/Plugins/SqlEnterpriseFormatter/formatcreateview.cpp b/Plugins/SqlEnterpriseFormatter/formatcreateview.cpp new file mode 100644 index 0000000..faec87a --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreateview.cpp @@ -0,0 +1,26 @@ +#include "formatcreateview.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqliteselect.h" + +FormatCreateView::FormatCreateView(SqliteCreateView* createView) : + createView(createView) +{ +} + +void FormatCreateView::formatInternal() +{ + withKeyword("CREATE"); + if (createView->tempKw) + withKeyword("TEMP"); + else if (createView->temporaryKw) + withKeyword("TEMPORARY"); + + withKeyword("VIEW"); + if (createView->ifNotExists) + withKeyword("IF").withKeyword("NOT").withKeyword("EXISTS"); + + if (dialect == Dialect::Sqlite3 && !createView->database.isNull()) + withId(createView->database).withIdDot(); + + withId(createView->view).withKeyword("AS").withStatement(createView->select).withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatcreateview.h b/Plugins/SqlEnterpriseFormatter/formatcreateview.h new file mode 100644 index 0000000..77f6219 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreateview.h @@ -0,0 +1,20 @@ +#ifndef FORMATCREATEVIEW_H +#define FORMATCREATEVIEW_H + +#include "formatstatement.h" + +class SqliteCreateView; + +class FormatCreateView : public FormatStatement +{ + public: + FormatCreateView(SqliteCreateView* createView); + + protected: + void formatInternal(); + + private: + SqliteCreateView* createView = nullptr; +}; + +#endif // FORMATCREATEVIEW_H diff --git a/Plugins/SqlEnterpriseFormatter/formatcreatevirtualtable.cpp b/Plugins/SqlEnterpriseFormatter/formatcreatevirtualtable.cpp new file mode 100644 index 0000000..d291eda --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreatevirtualtable.cpp @@ -0,0 +1,106 @@ +#include "formatcreatevirtualtable.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/lexer.h" + +FormatCreateVirtualTable::FormatCreateVirtualTable(SqliteCreateVirtualTable* cvt) : + cvt(cvt) +{ +} + +void FormatCreateVirtualTable::formatInternal() +{ + withKeyword("CREATE").withKeyword("VIRTUAL").withKeyword("TABLE"); + if (cvt->ifNotExistsKw) + withKeyword("IF").withKeyword("NOT").withKeyword("EXISTS"); + + if (!cvt->database.isNull()) + withId(cvt->database).withIdDot(); + + withId(cvt->table).withKeyword("USING").withId(cvt->module); + if (!cvt->args.isEmpty()) + { + withParDefLeft(); + int i = 0; + for (const QString& arg : cvt->args) + { + if (i > 0) + withListComma(); + + for (const TokenPtr& tk : Lexer::tokenize(arg, Dialect::Sqlite3)) + handleToken(tk); + + i++; + } + withParDefRight(); + } + + withSemicolon(); +} + +void FormatCreateVirtualTable::handleToken(const TokenPtr& token) +{ + switch (token->type) + { + case Token::OTHER: + withId(token->value); + break; + case Token::STRING: + withString(token->value); + break; + case Token::COMMENT: + // TODO Format comment here + break; + case Token::FLOAT: + withFloat(token->value.toDouble()); + break; + case Token::INTEGER: + withInteger(token->value.toInt()); + break; + case Token::BIND_PARAM: + withBindParam(token->value); + break; + case Token::OPERATOR: + withOperator(token->value); + break; + case Token::PAR_LEFT: + withParDefLeft(); + break; + case Token::PAR_RIGHT: + withParDefRight(); + break; + case Token::BLOB: + withBlob(token->value); + break; + case Token::KEYWORD: + withKeyword(token->value); + break; + case Token::SPACE: + case Token::INVALID: + case Token::CTX_COLUMN: + case Token::CTX_TABLE: + case Token::CTX_DATABASE: + case Token::CTX_FUNCTION: + case Token::CTX_COLLATION: + case Token::CTX_INDEX: + case Token::CTX_TRIGGER: + case Token::CTX_VIEW: + case Token::CTX_JOIN_OPTS: + case Token::CTX_TABLE_NEW: + case Token::CTX_INDEX_NEW: + case Token::CTX_VIEW_NEW: + case Token::CTX_TRIGGER_NEW: + case Token::CTX_ALIAS: + case Token::CTX_TRANSACTION: + case Token::CTX_COLUMN_NEW: + case Token::CTX_COLUMN_TYPE: + case Token::CTX_CONSTRAINT: + case Token::CTX_FK_MATCH: + case Token::CTX_PRAGMA: + case Token::CTX_ROWID_KW: + case Token::CTX_NEW_KW: + case Token::CTX_OLD_KW: + case Token::CTX_ERROR_MESSAGE: + break; + } +} + diff --git a/Plugins/SqlEnterpriseFormatter/formatcreatevirtualtable.h b/Plugins/SqlEnterpriseFormatter/formatcreatevirtualtable.h new file mode 100644 index 0000000..ad99d39 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatcreatevirtualtable.h @@ -0,0 +1,23 @@ +#ifndef FORMATCREATEVIRTUALTABLE_H +#define FORMATCREATEVIRTUALTABLE_H + +#include "formatstatement.h" +#include "parser/token.h" + +class SqliteCreateVirtualTable; + +class FormatCreateVirtualTable : public FormatStatement +{ + public: + FormatCreateVirtualTable(SqliteCreateVirtualTable* cvt); + + protected: + void formatInternal(); + + private: + void handleToken(const TokenPtr& token); + + SqliteCreateVirtualTable* cvt = nullptr; +}; + +#endif // FORMATCREATEVIRTUALTABLE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatdelete.cpp b/Plugins/SqlEnterpriseFormatter/formatdelete.cpp new file mode 100644 index 0000000..ca6ac5b --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdelete.cpp @@ -0,0 +1,33 @@ +#include "formatdelete.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqliteexpr.h" +#include "formatwith.h" + +FormatDelete::FormatDelete(SqliteDelete* del) : + del(del) +{ +} + +void FormatDelete::formatInternal() +{ + if (del->with) + withStatement(del->with); + + markKeywordLineUp("DELETE FROM"); + + withKeyword("DELETE").withKeyword("FROM"); + if (!del->database.isNull()) + withId(del->database).withIdDot(); + + withId(del->table); + + if (del->indexedByKw) + withKeyword("INDEXED").withKeyword("BY").withId(del->indexedBy); + else if (del->notIndexedKw) + withKeyword("NOT").withKeyword("INDEXED"); + + if (del->where) + withNewLine().withLinedUpKeyword("WHERE").withStatement(del->where); + + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatdelete.h b/Plugins/SqlEnterpriseFormatter/formatdelete.h new file mode 100644 index 0000000..65489f8 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdelete.h @@ -0,0 +1,20 @@ +#ifndef FORMATDELETE_H +#define FORMATDELETE_H + +#include "formatstatement.h" + +class SqliteDelete; + +class FormatDelete : public FormatStatement +{ + public: + FormatDelete(SqliteDelete* del); + + protected: + void formatInternal(); + + private: + SqliteDelete* del = nullptr; +}; + +#endif // FORMATDELETE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatdetach.cpp b/Plugins/SqlEnterpriseFormatter/formatdetach.cpp new file mode 100644 index 0000000..e788da7 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdetach.cpp @@ -0,0 +1,18 @@ +#include "formatdetach.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqliteexpr.h" + +FormatDetach::FormatDetach(SqliteDetach* detach) : + detach(detach) +{ +} + +void FormatDetach::formatInternal() +{ + withKeyword("DETACH"); + + if (detach->databaseKw) + withKeyword("DATABASE"); + + withStatement(detach->name).withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatdetach.h b/Plugins/SqlEnterpriseFormatter/formatdetach.h new file mode 100644 index 0000000..5f73a2c --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdetach.h @@ -0,0 +1,20 @@ +#ifndef FORMATDETACH_H +#define FORMATDETACH_H + +#include "formatstatement.h" + +class SqliteDetach; + +class FormatDetach : public FormatStatement +{ + public: + FormatDetach(SqliteDetach* detach); + + protected: + void formatInternal(); + + private: + SqliteDetach* detach = nullptr; +}; + +#endif // FORMATDETACH_H diff --git a/Plugins/SqlEnterpriseFormatter/formatdropindex.cpp b/Plugins/SqlEnterpriseFormatter/formatdropindex.cpp new file mode 100644 index 0000000..1e85bb8 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdropindex.cpp @@ -0,0 +1,20 @@ +#include "formatdropindex.h" +#include "parser/ast/sqlitedropindex.h" + +FormatDropIndex::FormatDropIndex(SqliteDropIndex* dropIndex) : + dropIndex(dropIndex) +{ +} + +void FormatDropIndex::formatInternal() +{ + withKeyword("DROP").withKeyword("INDEX"); + + if (dropIndex->ifExistsKw) + withKeyword("IF").withKeyword("EXISTS"); + + if (!dropIndex->database.isNull()) + withId(dropIndex->database).withIdDot(); + + withId(dropIndex->index).withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatdropindex.h b/Plugins/SqlEnterpriseFormatter/formatdropindex.h new file mode 100644 index 0000000..2ecf51c --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdropindex.h @@ -0,0 +1,20 @@ +#ifndef FORMATDROPINDEX_H +#define FORMATDROPINDEX_H + +#include "formatstatement.h" + +class SqliteDropIndex; + +class FormatDropIndex : public FormatStatement +{ + public: + FormatDropIndex(SqliteDropIndex* dropIndex); + + protected: + void formatInternal(); + + private: + SqliteDropIndex* dropIndex = nullptr; +}; + +#endif // FORMATDROPINDEX_H diff --git a/Plugins/SqlEnterpriseFormatter/formatdroptable.cpp b/Plugins/SqlEnterpriseFormatter/formatdroptable.cpp new file mode 100644 index 0000000..775adfe --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdroptable.cpp @@ -0,0 +1,21 @@ +#include "formatdroptable.h" +#include "parser/ast/sqlitedroptable.h" + +FormatDropTable::FormatDropTable(SqliteDropTable* dropTable) : + dropTable(dropTable) +{ +} + +void FormatDropTable::formatInternal() +{ + withKeyword("DROP").withKeyword("TABLE"); + + if (dropTable->ifExistsKw) + withKeyword("IF").withKeyword("EXISTS"); + + if (!dropTable->database.isNull()) + withId(dropTable->database).withIdDot(); + + withId(dropTable->table).withSemicolon(); + +} diff --git a/Plugins/SqlEnterpriseFormatter/formatdroptable.h b/Plugins/SqlEnterpriseFormatter/formatdroptable.h new file mode 100644 index 0000000..1c0d456 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdroptable.h @@ -0,0 +1,20 @@ +#ifndef FORMATDROPTABLE_H +#define FORMATDROPTABLE_H + +#include "formatstatement.h" + +class SqliteDropTable; + +class FormatDropTable : public FormatStatement +{ + public: + FormatDropTable(SqliteDropTable* dropTable); + + protected: + void formatInternal(); + + private: + SqliteDropTable* dropTable = nullptr; +}; + +#endif // FORMATDROPTABLE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatdroptrigger.cpp b/Plugins/SqlEnterpriseFormatter/formatdroptrigger.cpp new file mode 100644 index 0000000..ca8caa8 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdroptrigger.cpp @@ -0,0 +1,21 @@ +#include "formatdroptrigger.h" +#include "parser/ast/sqlitedroptrigger.h" + +FormatDropTrigger::FormatDropTrigger(SqliteDropTrigger* dropTrig) : + dropTrig(dropTrig) +{ +} + +void FormatDropTrigger::formatInternal() +{ + withKeyword("DROP").withKeyword("TRIGGER"); + + if (dropTrig->ifExistsKw) + withKeyword("IF").withKeyword("EXISTS"); + + if (!dropTrig->database.isNull()) + withId(dropTrig->database).withIdDot(); + + withId(dropTrig->trigger).withSemicolon(); + +} diff --git a/Plugins/SqlEnterpriseFormatter/formatdroptrigger.h b/Plugins/SqlEnterpriseFormatter/formatdroptrigger.h new file mode 100644 index 0000000..26f9ebe --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdroptrigger.h @@ -0,0 +1,20 @@ +#ifndef FORMATDROPTRIGGER_H +#define FORMATDROPTRIGGER_H + +#include "formatstatement.h" + +class SqliteDropTrigger; + +class FormatDropTrigger : public FormatStatement +{ + public: + FormatDropTrigger(SqliteDropTrigger* dropTrig); + + protected: + void formatInternal(); + + private: + SqliteDropTrigger* dropTrig = nullptr; +}; + +#endif // FORMATDROPTRIGGER_H diff --git a/Plugins/SqlEnterpriseFormatter/formatdropview.cpp b/Plugins/SqlEnterpriseFormatter/formatdropview.cpp new file mode 100644 index 0000000..965d607 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdropview.cpp @@ -0,0 +1,20 @@ +#include "formatdropview.h" +#include "parser/ast/sqlitedropview.h" + +FormatDropView::FormatDropView(SqliteDropView* dropView) : + dropView(dropView) +{ +} + +void FormatDropView::formatInternal() +{ + withKeyword("DROP").withKeyword("VIEW"); + + if (dropView->ifExistsKw) + withKeyword("IF").withKeyword("EXISTS"); + + if (!dropView->database.isNull()) + withId(dropView->database).withIdDot(); + + withId(dropView->view).withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatdropview.h b/Plugins/SqlEnterpriseFormatter/formatdropview.h new file mode 100644 index 0000000..ab724f8 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatdropview.h @@ -0,0 +1,20 @@ +#ifndef FORMATDROPVIEW_H +#define FORMATDROPVIEW_H + +#include "formatstatement.h" + +class SqliteDropView; + +class FormatDropView : public FormatStatement +{ + public: + FormatDropView(SqliteDropView* dropView); + + protected: + void formatInternal(); + + private: + SqliteDropView* dropView = nullptr; +}; + +#endif // FORMATDROPVIEW_H diff --git a/Plugins/SqlEnterpriseFormatter/formatempty.cpp b/Plugins/SqlEnterpriseFormatter/formatempty.cpp new file mode 100644 index 0000000..976694e --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatempty.cpp @@ -0,0 +1,11 @@ +#include "formatempty.h" + +FormatEmpty::FormatEmpty(SqliteEmptyQuery* eq) : + eq(eq) +{ +} + +void FormatEmpty::formatInternal() +{ + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatempty.h b/Plugins/SqlEnterpriseFormatter/formatempty.h new file mode 100644 index 0000000..3279925 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatempty.h @@ -0,0 +1,20 @@ +#ifndef FORMATEMPTY_H +#define FORMATEMPTY_H + +#include "formatstatement.h" + +class SqliteEmptyQuery; + +class FormatEmpty : public FormatStatement +{ + public: + FormatEmpty(SqliteEmptyQuery* eq); + + protected: + void formatInternal(); + + private: + SqliteEmptyQuery* eq = nullptr; +}; + +#endif // FORMATEMPTY_H diff --git a/Plugins/SqlEnterpriseFormatter/formatexpr.cpp b/Plugins/SqlEnterpriseFormatter/formatexpr.cpp new file mode 100644 index 0000000..2099126 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatexpr.cpp @@ -0,0 +1,201 @@ +#include "formatexpr.h" +#include "sqlenterpriseformatter.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteraise.h" +#include "sqlenterpriseformatter.h" + +FormatExpr::FormatExpr(SqliteExpr* expr) : + expr(expr) +{ +} + +void FormatExpr::formatInternal() +{ + static QStringList nlBiOp = {"AND", "OR"}; + + switch (expr->mode) + { + case SqliteExpr::Mode::null: + break; + case SqliteExpr::Mode::LITERAL_VALUE: + { + if (expr->literalNull) + withKeyword("NULL"); + else + withLiteral(expr->literalValue); + break; + } + case SqliteExpr::Mode::CTIME: + withKeyword(expr->ctime.toUpper()); + break; + case SqliteExpr::Mode::BIND_PARAM: + withBindParam(expr->bindParam); + break; + case SqliteExpr::Mode::ID: + { + if (!expr->database.isNull()) + withId(expr->database).withIdDot(); + + if (!expr->table.isNull()) + withId(expr->table).withIdDot(); + + if (expr->possibleDoubleQuotedString) + withStringOrId(expr->column); + else + withId(expr->column); + break; + } + case SqliteExpr::Mode::UNARY_OP: + { + // Operator can be a keyword + QString opStr = cfg->SqlEnterpriseFormatter.UppercaseKeywords.get() ? expr->unaryOp.toUpper() : expr->unaryOp.toLower(); + withOperator(opStr).withStatement(expr->expr1, "unaryOp"); + break; + } + case SqliteExpr::Mode::BINARY_OP: + { + bool multiLine = nlBiOp.contains(expr->binaryOp.toUpper()); + + // Operator can be a keyword + QString opStr = cfg->SqlEnterpriseFormatter.UppercaseKeywords.get() ? expr->binaryOp.toUpper() : expr->binaryOp.toLower(); + withStatement(expr->expr1, "binaryOp1").withOperator(opStr); + + if (multiLine) + withNewLine().withIncrIndent("binaryOp1"); + + withStatement(expr->expr2, "binaryOp2"); + if (multiLine) + withDecrIndent(); + break; + } + case SqliteExpr::Mode::FUNCTION: + withFuncId(expr->function).withParFuncLeft().withStatementList(expr->exprList, "funcArgs", FormatStatement::ListSeparator::EXPR_COMMA).withParFuncRight(); + break; + case SqliteExpr::Mode::SUB_EXPR: + withParExprLeft().withStatement(expr->expr1).withParExprRight(); + break; + case SqliteExpr::Mode::CAST: + withKeyword("CAST").withParExprLeft().withStatement(expr->expr1).withKeyword("AS") + .withStatement(expr->columnType, "colType").withParExprRight(); + break; + case SqliteExpr::Mode::COLLATE: + withStatement(expr->expr1).withKeyword("COLLATE").withId(expr->collation); + break; + case SqliteExpr::Mode::LIKE: + { + withStatement(expr->expr1); + if (expr->notKw) + withKeyword("NOT"); + + withKeyword(SqliteExpr::likeOp(expr->like)).withStatement(expr->expr2, "like"); + + if (expr->expr3) + withKeyword("ESCAPE").withStatement(expr->expr3, "likeEscape"); + + break; + } + case SqliteExpr::Mode::NULL_: + withKeyword("NULL"); + break; + case SqliteExpr::Mode::NOTNULL: + { + switch (expr->notNull) + { + case SqliteExpr::NotNull::ISNULL: + withKeyword("ISNULL"); + break; + case SqliteExpr::NotNull::NOT_NULL: + withKeyword("NOT").withKeyword("NULL"); + break; + case SqliteExpr::NotNull::NOTNULL: + withKeyword("NOTNULL"); + break; + case SqliteExpr::NotNull::null: + break; + } + break; + } + case SqliteExpr::Mode::IS: + { + withStatement(expr->expr1).withKeyword("IS"); + if (expr->notKw) + withKeyword("NOT"); + + withStatement(expr->expr2, "is"); + break; + } + case SqliteExpr::Mode::BETWEEN: + { + withStatement(expr->expr1); + + if (expr->notKw) + withKeyword("NOT"); + + withKeyword("BETWEEN").withStatement(expr->expr2, "between1").withKeyword("AND").withStatement(expr->expr3, "between2"); + break; + } + case SqliteExpr::Mode::IN: + { + withStatement(expr->expr1); + + if (expr->notKw) + withKeyword("NOT"); + + withKeyword("IN"); + if (expr->select) + { + withParDefLeft().withStatement(expr->select).withParDefRight(); + } + else if (expr->exprList.size() > 0) + { + withParExprLeft().withStatementList(expr->exprList).withParExprRight(); + } + else + { + if (!expr->database.isNull()) + withId(expr->database).withIdDot(); + + withId(expr->table); + } + break; + } + case SqliteExpr::Mode::EXISTS: + withKeyword("EXISTS").withParDefLeft().withStatement(expr->select).withParDefRight(); + break; + case SqliteExpr::Mode::CASE: + { + withKeyword("CASE"); + if (expr->expr1) + withStatement(expr->expr1, "case"); + + bool then = false; + foreach (SqliteExpr* expr, expr->exprList) + { + if (then) + withKeyword("THEN"); + else + withKeyword("WHEN"); + + withIncrIndent("case"); + withStatement(expr); + withDecrIndent(); + + then = !then; + } + + if (expr->expr2) + withKeyword("ELSE").withIncrIndent("case").withStatement(expr->expr2).withDecrIndent(); + + withKeyword("END"); + break; + } + case SqliteExpr::Mode::SUB_SELECT: + withParDefLeft().withStatement(expr->select).withParDefRight(); + break; + case SqliteExpr::Mode::RAISE: + withStatement(expr->raiseFunction); + break; + } +} diff --git a/Plugins/SqlEnterpriseFormatter/formatexpr.h b/Plugins/SqlEnterpriseFormatter/formatexpr.h new file mode 100644 index 0000000..2712adc --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatexpr.h @@ -0,0 +1,20 @@ +#ifndef FORMATEXPR_H +#define FORMATEXPR_H + +#include "formatstatement.h" + +class SqliteExpr; + +class FormatExpr : public FormatStatement +{ + public: + FormatExpr(SqliteExpr* expr); + + protected: + void formatInternal(); + + private: + SqliteExpr* expr = nullptr; +}; + +#endif // FORMATEXPR_H diff --git a/Plugins/SqlEnterpriseFormatter/formatforeignkey.cpp b/Plugins/SqlEnterpriseFormatter/formatforeignkey.cpp new file mode 100644 index 0000000..6b28a86 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatforeignkey.cpp @@ -0,0 +1,78 @@ +#include "formatforeignkey.h" + +FormatForeignKey::FormatForeignKey(SqliteForeignKey* fk) : + fk(fk) +{ +} + +void FormatForeignKey::formatInternal() +{ + withKeyword("REFERENCES").withId(fk->foreignTable); + + if (fk->indexedColumns.size() > 0) + withParExprLeft().withStatementList(fk->indexedColumns).withParExprRight(); + + if (fk->conditions.size() > 0) + { + markAndKeepIndent("constr_conditions").withStatementList(fk->conditions, QString(), ListSeparator::NEW_LINE).withDecrIndent(); + } + + if (fk->deferrable != SqliteDeferrable::null) + { + if (fk->deferrable == SqliteDeferrable::NOT_DEFERRABLE) + withKeyword("NOT").withKeyword("DEFERRABLE"); + else if (fk->deferrable == SqliteDeferrable::DEFERRABLE) + withKeyword("DEFERRABLE"); + + if (fk->initially != SqliteInitially::null) + withKeyword("INITIALLY").withKeyword(sqliteInitially(fk->initially)); + } +} + + +FormatForeignKeyCondition::FormatForeignKeyCondition(SqliteForeignKey::Condition* cond) : + cond(cond) +{ +} + +void FormatForeignKeyCondition::formatInternal() +{ + switch (cond->action) + { + case SqliteForeignKey::Condition::UPDATE: + withKeyword("ON").withKeyword("UPDATE"); + break; + case SqliteForeignKey::Condition::INSERT: + withKeyword("ON").withKeyword("INSERT"); + break; + case SqliteForeignKey::Condition::DELETE: + withKeyword("ON").withKeyword("DELETE"); + break; + case SqliteForeignKey::Condition::MATCH: + withKeyword("MATCH").withId(cond->name); + return; + } + formatReaction(); +} + +void FormatForeignKeyCondition::formatReaction() +{ + switch (cond->reaction) + { + case SqliteForeignKey::Condition::SET_NULL: + withKeyword("SET").withKeyword("NULL"); + break; + case SqliteForeignKey::Condition::SET_DEFAULT: + withKeyword("SET").withKeyword("DEFAULT"); + break; + case SqliteForeignKey::Condition::CASCADE: + withKeyword("CASCADE"); + break; + case SqliteForeignKey::Condition::RESTRICT: + withKeyword("RESTRICT"); + break; + case SqliteForeignKey::Condition::NO_ACTION: + withKeyword("NO").withKeyword("ACTION"); + break; + } +} diff --git a/Plugins/SqlEnterpriseFormatter/formatforeignkey.h b/Plugins/SqlEnterpriseFormatter/formatforeignkey.h new file mode 100644 index 0000000..771fdba --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatforeignkey.h @@ -0,0 +1,33 @@ +#ifndef FORMATFOREIGNKEY_H +#define FORMATFOREIGNKEY_H + +#include "formatstatement.h" +#include "parser/ast/sqliteforeignkey.h" + +class FormatForeignKey : public FormatStatement +{ + public: + FormatForeignKey(SqliteForeignKey* fk); + + protected: + void formatInternal(); + + private: + SqliteForeignKey* fk = nullptr; +}; + +class FormatForeignKeyCondition : public FormatStatement +{ + public: + FormatForeignKeyCondition(SqliteForeignKey::Condition* cond); + + protected: + void formatInternal(); + + private: + void formatReaction(); + + SqliteForeignKey::Condition* cond = nullptr; +}; + +#endif // FORMATFOREIGNKEY_H diff --git a/Plugins/SqlEnterpriseFormatter/formatindexedcolumn.cpp b/Plugins/SqlEnterpriseFormatter/formatindexedcolumn.cpp new file mode 100644 index 0000000..eef8309 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatindexedcolumn.cpp @@ -0,0 +1,17 @@ +#include "formatindexedcolumn.h" +#include "parser/ast/sqliteindexedcolumn.h" + +FormatIndexedColumn::FormatIndexedColumn(SqliteIndexedColumn* idxCol) : + idxCol(idxCol) +{ +} + + +void FormatIndexedColumn::formatInternal() +{ + withId(idxCol->name); + if (!idxCol->collate.isNull()) + withKeyword("COLLATE").withId(idxCol->collate); + + withSortOrder(idxCol->sortOrder); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatindexedcolumn.h b/Plugins/SqlEnterpriseFormatter/formatindexedcolumn.h new file mode 100644 index 0000000..4bfb7dd --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatindexedcolumn.h @@ -0,0 +1,20 @@ +#ifndef FORMATINDEXEDCOLUMN_H +#define FORMATINDEXEDCOLUMN_H + +#include "formatstatement.h" + +class SqliteIndexedColumn; + +class FormatIndexedColumn : public FormatStatement +{ + public: + FormatIndexedColumn(SqliteIndexedColumn* idxCol); + + protected: + void formatInternal(); + + private: + SqliteIndexedColumn* idxCol = nullptr; +}; + +#endif // FORMATINDEXEDCOLUMN_H diff --git a/Plugins/SqlEnterpriseFormatter/formatinsert.cpp b/Plugins/SqlEnterpriseFormatter/formatinsert.cpp new file mode 100644 index 0000000..1ff0535 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatinsert.cpp @@ -0,0 +1,54 @@ +#include "formatinsert.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteinsert.h" +#include "formatwith.h" + +FormatInsert::FormatInsert(SqliteInsert* insert) : + insert(insert) +{ +} + +void FormatInsert::formatInternal() +{ + if (insert->replaceKw) + { + withStatement(insert->with); + withKeyword("REPLACE"); + } + else + { + withStatement(insert->with); + withKeyword("INSERT"); + if (insert->onConflict != SqliteConflictAlgo::null) + withKeyword("OR").withKeyword(sqliteConflictAlgo(insert->onConflict)); + } + + withKeyword("INTO"); + + if (!insert->database.isNull()) + withId(insert->database); + + withId(insert->table); + + if (insert->defaultValuesKw) + { + withKeyword("DEFAULT").withKeyword("VALUES"); + } + else + { + markAndKeepIndent("insertCols"); + if (insert->columnNames.size() > 0) + withParDefLeft().withIdList(insert->columnNames).withParDefRight(); + + if (insert->select) + { + withStatement(insert->select); + } + else if (dialect == Dialect::Sqlite2) // Sqlite2 uses classic single row values + { + withKeyword("VALUES").withParDefLeft().withStatementList(insert->values).withParDefRight(); + } + withDecrIndent(); + } + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatinsert.h b/Plugins/SqlEnterpriseFormatter/formatinsert.h new file mode 100644 index 0000000..1d85c6a --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatinsert.h @@ -0,0 +1,20 @@ +#ifndef FORMATINSERT_H +#define FORMATINSERT_H + +#include "formatstatement.h" + +class SqliteInsert; + +class FormatInsert : public FormatStatement +{ + public: + FormatInsert(SqliteInsert* insert); + + protected: + void formatInternal(); + + private: + SqliteInsert* insert = nullptr; +}; + +#endif // FORMATINSERT_H diff --git a/Plugins/SqlEnterpriseFormatter/formatlimit.cpp b/Plugins/SqlEnterpriseFormatter/formatlimit.cpp new file mode 100644 index 0000000..7b4b4d6 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatlimit.cpp @@ -0,0 +1,24 @@ +#include "formatlimit.h" +#include "parser/ast/sqlitelimit.h" +#include "parser/ast/sqliteexpr.h" + +FormatLimit::FormatLimit(SqliteLimit *limit) : + limit(limit) +{ +} + +void FormatLimit::formatInternal() +{ + if (limit->limit) + withStatement(limit->limit); + + if (limit->offset) + { + if (limit->offsetKw) + withKeyword("OFFSET"); + else + withCommaOper(); + + withStatement(limit->offset); + } +} diff --git a/Plugins/SqlEnterpriseFormatter/formatlimit.h b/Plugins/SqlEnterpriseFormatter/formatlimit.h new file mode 100644 index 0000000..bfd99f1 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatlimit.h @@ -0,0 +1,19 @@ +#ifndef FORMATLIMIT_H +#define FORMATLIMIT_H + +#include "formatstatement.h" + +class SqliteLimit; + +class FormatLimit : public FormatStatement +{ + public: + FormatLimit(SqliteLimit* limit); + + void formatInternal(); + + private: + SqliteLimit *limit = nullptr; +}; + +#endif // FORMATLIMIT_H diff --git a/Plugins/SqlEnterpriseFormatter/formatorderby.cpp b/Plugins/SqlEnterpriseFormatter/formatorderby.cpp new file mode 100644 index 0000000..d7b2def --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatorderby.cpp @@ -0,0 +1,15 @@ +#include "formatorderby.h" +#include "parser/ast/sqliteorderby.h" +#include "parser/ast/sqliteexpr.h" + +FormatOrderBy::FormatOrderBy(SqliteOrderBy* orderBy) : + orderBy(orderBy) +{ +} + +void FormatOrderBy::formatInternal() +{ + withStatement(orderBy->expr); + if (orderBy->order != SqliteSortOrder::null) + withKeyword(sqliteSortOrder(orderBy->order)); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatorderby.h b/Plugins/SqlEnterpriseFormatter/formatorderby.h new file mode 100644 index 0000000..ce380e5 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatorderby.h @@ -0,0 +1,19 @@ +#ifndef FORMATORDERBY_H +#define FORMATORDERBY_H + +#include "formatstatement.h" + +class SqliteOrderBy; + +class FormatOrderBy : public FormatStatement +{ + public: + FormatOrderBy(SqliteOrderBy *orderBy); + + void formatInternal(); + + private: + SqliteOrderBy *orderBy = nullptr; +}; + +#endif // FORMATORDERBY_H diff --git a/Plugins/SqlEnterpriseFormatter/formatpragma.cpp b/Plugins/SqlEnterpriseFormatter/formatpragma.cpp new file mode 100644 index 0000000..0b6491a --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatpragma.cpp @@ -0,0 +1,24 @@ +#include "formatpragma.h" +#include "parser/ast/sqlitepragma.h" + +FormatPragma::FormatPragma(SqlitePragma* pragma) : + pragma(pragma) +{ +} + +void FormatPragma::formatInternal() +{ + withKeyword("PRAGMA"); + + if (!pragma->database.isNull()) + withId(pragma->database).withIdDot(); + + withId(pragma->pragmaName); + + if (pragma->equalsOp) + withOperator("=").withLiteral(pragma->value); + else if (pragma->parenthesis) + withParExprLeft().withLiteral(pragma->value).withParExprRight(); + + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatpragma.h b/Plugins/SqlEnterpriseFormatter/formatpragma.h new file mode 100644 index 0000000..c720b35 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatpragma.h @@ -0,0 +1,20 @@ +#ifndef FORMATPRAGMA_H +#define FORMATPRAGMA_H + +#include "formatstatement.h" + +class SqlitePragma; + +class FormatPragma : public FormatStatement +{ + public: + FormatPragma(SqlitePragma* pragma); + + protected: + void formatInternal(); + + private: + SqlitePragma* pragma = nullptr; +}; + +#endif // FORMATPRAGMA_H diff --git a/Plugins/SqlEnterpriseFormatter/formatraise.cpp b/Plugins/SqlEnterpriseFormatter/formatraise.cpp new file mode 100644 index 0000000..be3787e --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatraise.cpp @@ -0,0 +1,16 @@ +#include "formatraise.h" +#include "parser/ast/sqliteraise.h" + +FormatRaise::FormatRaise(SqliteRaise *raise) : + raise(raise) +{ +} + +void FormatRaise::formatInternal() +{ + withKeyword("RAISE").withParFuncLeft().withKeyword(SqliteRaise::raiseType(raise->type)); + if (raise->type != SqliteRaise::Type::IGNORE) + withCommaOper().withString(raise->message); + + withParFuncRight(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatraise.h b/Plugins/SqlEnterpriseFormatter/formatraise.h new file mode 100644 index 0000000..7292fba --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatraise.h @@ -0,0 +1,20 @@ +#ifndef FORMATRAISE_H +#define FORMATRAISE_H + +#include "formatstatement.h" + +class SqliteRaise; + +class FormatRaise : public FormatStatement +{ + public: + FormatRaise(SqliteRaise* raise); + + protected: + void formatInternal(); + + private: + SqliteRaise* raise = nullptr; +}; + +#endif // FORMATRAISE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatreindex.cpp b/Plugins/SqlEnterpriseFormatter/formatreindex.cpp new file mode 100644 index 0000000..441032e --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatreindex.cpp @@ -0,0 +1,16 @@ +#include "formatreindex.h" +#include "parser/ast/sqlitereindex.h" + +FormatReindex::FormatReindex(SqliteReindex* reindex) : + reindex(reindex) +{ +} + +void FormatReindex::formatInternal() +{ + withKeyword("REINDEX"); + if (!reindex->database.isNull()) + withId(reindex->database).withIdDot(); + + withId(reindex->table).withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatreindex.h b/Plugins/SqlEnterpriseFormatter/formatreindex.h new file mode 100644 index 0000000..018d422 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatreindex.h @@ -0,0 +1,20 @@ +#ifndef FORMATREINDEX_H +#define FORMATREINDEX_H + +#include "formatstatement.h" + +class SqliteReindex; + +class FormatReindex : public FormatStatement +{ + public: + FormatReindex(SqliteReindex* reindex); + + protected: + void formatInternal(); + + private: + SqliteReindex* reindex = nullptr; +}; + +#endif // FORMATREINDEX_H diff --git a/Plugins/SqlEnterpriseFormatter/formatrelease.cpp b/Plugins/SqlEnterpriseFormatter/formatrelease.cpp new file mode 100644 index 0000000..52c0b24 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatrelease.cpp @@ -0,0 +1,16 @@ +#include "formatrelease.h" +#include "parser/ast/sqliterelease.h" + +FormatRelease::FormatRelease(SqliteRelease* release) : + release(release) +{ +} + +void FormatRelease::formatInternal() +{ + withKeyword("RELEASE"); + if (release->savepointKw) + withKeyword("SAVEPOINT"); + + withId(release->name).withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatrelease.h b/Plugins/SqlEnterpriseFormatter/formatrelease.h new file mode 100644 index 0000000..3df3d1a --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatrelease.h @@ -0,0 +1,20 @@ +#ifndef FORMATRELEASE_H +#define FORMATRELEASE_H + +#include "formatstatement.h" + +class SqliteRelease; + +class FormatRelease : public FormatStatement +{ + public: + FormatRelease(SqliteRelease* release); + + protected: + void formatInternal(); + + private: + SqliteRelease* release = nullptr; +}; + +#endif // FORMATRELEASE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatrollback.cpp b/Plugins/SqlEnterpriseFormatter/formatrollback.cpp new file mode 100644 index 0000000..c55f5cc --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatrollback.cpp @@ -0,0 +1,24 @@ +#include "formatrollback.h" +#include "parser/ast/sqliterollback.h" + +FormatRollback::FormatRollback(SqliteRollback* rollback) : + rollback(rollback) +{ +} + +void FormatRollback::formatInternal() +{ + withKeyword("ROLLBACK"); + if (rollback->transactionKw) + withKeyword("TRANSACTION"); + + if (!rollback->name.isNull()) + { + withKeyword("TO"); + if (rollback->savepointKw) + withKeyword("SAVEPOINT"); + + withId(rollback->name); + } + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatrollback.h b/Plugins/SqlEnterpriseFormatter/formatrollback.h new file mode 100644 index 0000000..4a69e14 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatrollback.h @@ -0,0 +1,20 @@ +#ifndef FORMATROLLBACK_H +#define FORMATROLLBACK_H + +#include "formatstatement.h" + +class SqliteRollback; + +class FormatRollback : public FormatStatement +{ + public: + FormatRollback(SqliteRollback* rollback); + + protected: + void formatInternal(); + + private: + SqliteRollback* rollback = nullptr; +}; + +#endif // FORMATROLLBACK_H diff --git a/Plugins/SqlEnterpriseFormatter/formatsavepoint.cpp b/Plugins/SqlEnterpriseFormatter/formatsavepoint.cpp new file mode 100644 index 0000000..3f34679 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatsavepoint.cpp @@ -0,0 +1,12 @@ +#include "formatsavepoint.h" +#include "parser/ast/sqlitesavepoint.h" + +FormatSavepoint::FormatSavepoint(SqliteSavepoint* savepoint) : + savepoint(savepoint) +{ +} + +void FormatSavepoint::formatInternal() +{ + withKeyword("SAVEPOINT").withId(savepoint->name).withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatsavepoint.h b/Plugins/SqlEnterpriseFormatter/formatsavepoint.h new file mode 100644 index 0000000..f9f31c1 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatsavepoint.h @@ -0,0 +1,20 @@ +#ifndef FORMATSAVEPOINT_H +#define FORMATSAVEPOINT_H + +#include "formatstatement.h" + +class SqliteSavepoint; + +class FormatSavepoint : public FormatStatement +{ + public: + FormatSavepoint(SqliteSavepoint* savepoint); + + protected: + void formatInternal(); + + private: + SqliteSavepoint* savepoint = nullptr; +}; + +#endif // FORMATSAVEPOINT_H diff --git a/Plugins/SqlEnterpriseFormatter/formatselect.cpp b/Plugins/SqlEnterpriseFormatter/formatselect.cpp new file mode 100644 index 0000000..50aa8c2 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatselect.cpp @@ -0,0 +1,261 @@ +#include "formatselect.h" +#include "formatwith.h" +#include "parser/ast/sqlitewith.h" + +FormatSelect::FormatSelect(SqliteSelect* select) : + select(select) +{ +} + +void FormatSelect::formatInternal() +{ + markKeywordLineUp("SELECT"); + + if (select->with) + withStatement(select->with); + + for (SqliteSelect::Core* core : select->coreSelects) + { + switch (core->compoundOp) + { + case SqliteSelect::CompoundOperator::UNION: + withNewLine().withKeyword("UNION").withNewLine(); + break; + case SqliteSelect::CompoundOperator::UNION_ALL: + withNewLine().withKeyword("UNION ALL").withNewLine(); + break; + case SqliteSelect::CompoundOperator::INTERSECT: + withNewLine().withKeyword("INTERSECT").withNewLine(); + break; + case SqliteSelect::CompoundOperator::EXCEPT: + withNewLine().withKeyword("EXCEPT").withNewLine(); + break; + case SqliteSelect::CompoundOperator::null: + break; + } + withStatement(core); + } + + if (select->parentStatement() == nullptr) // it's not a subselect, it's top-level select + withSemicolon(); +} + +FormatSelectCore::FormatSelectCore(SqliteSelect::Core *core) : + core(core) +{ +} + +void FormatSelectCore::formatInternal() +{ + markKeywordLineUp("SELECT", "selectCore"); + + if (core->valuesMode) + { + withKeyword("VALUES").withParDefLeft().withStatementList(core->resultColumns).withParDefRight(); + return; + } + + withKeyword("SELECT"); + if (core->distinctKw) + withKeyword("DISTINCT"); + else if (core->allKw) + withKeyword("ALL"); + + withStatementList(core->resultColumns, "resultColumns"); + + if (core->from) + withNewLine().withLinedUpKeyword("FROM", "selectCore").withStatement(core->from, "source"); + + if (core->where) + withNewLine().withLinedUpKeyword("WHERE", "selectCore").withStatement(core->where, "conditions"); + + if (core->groupBy.size() > 0) + withNewLine().withLinedUpKeyword("GROUP", "selectCore").withKeyword("BY").withStatementList(core->groupBy, "grouping"); + + if (core->having) + withNewLine().withLinedUpKeyword("HAVING", "selectCore").withStatement(core->having, "having"); + + if (core->orderBy.size() > 0) + withNewLine().withLinedUpKeyword("ORDER", "selectCore").withKeyword("BY").withStatementList(core->orderBy, "order"); + + if (core->limit) + withNewLine().withLinedUpKeyword("LIMIT", "selectCore").withStatement(core->limit, "limit"); +} + +FormatSelectCoreResultColumn::FormatSelectCoreResultColumn(SqliteSelect::Core::ResultColumn *resCol) : + resCol(resCol) +{ +} + +void FormatSelectCoreResultColumn::formatInternal() +{ + if (resCol->star) + { + if (!resCol->table.isNull()) + { + withId(resCol->table).withIdDot(); + } + withStar(); + } + else + { + withStatement(resCol->expr, "column"); + if (!resCol->alias.isNull()) + { + withIncrIndent("column"); + if (resCol->asKw) + withKeyword("AS"); + + withId(resCol->alias).withDecrIndent(); + } + } +} + +FormatSelectCoreSingleSource::FormatSelectCoreSingleSource(SqliteSelect::Core::SingleSource* singleSource) : + singleSource(singleSource) +{ +} + +void FormatSelectCoreSingleSource::formatInternal() +{ + if (!singleSource->table.isNull()) + { + if (!singleSource->database.isNull()) + withId(singleSource->database).withIdDot(); + + withId(singleSource->table); + + if (!singleSource->alias.isNull()) + { + if (singleSource->asKw) + withKeyword("AS"); + + withId(singleSource->alias); + + if (singleSource->indexedByKw) + withKeyword("INDEXED").withKeyword("BY").withId(singleSource->indexedBy); + else if (singleSource->notIndexedKw) + withKeyword("NOT").withKeyword("INDEXED"); + } + } + else if (singleSource->select) + { + withParDefLeft().withStatement(singleSource->select).withParDefRight(); + if (!singleSource->alias.isNull()) + { + if (singleSource->asKw) + withKeyword("AS"); + + withId(singleSource->alias); + } + } + else + { + withParDefLeft().withStatement(singleSource->joinSource).withParDefRight(); + } +} + +FormatSelectCoreJoinOp::FormatSelectCoreJoinOp(SqliteSelect::Core::JoinOp* joinOp) : + joinOp(joinOp) +{ +} + +void FormatSelectCoreJoinOp::formatInternal() +{ + if (joinOp->comma) + { + withListComma(); + return; + } + + withNewLine(); + QStringList keywords; + switch (dialect) + { + case Dialect::Sqlite3: + { + if (joinOp->naturalKw) + keywords << "NATURAL"; + + if (joinOp->leftKw) + { + keywords << "LEFT"; + if (joinOp->outerKw) + keywords << "OUTER"; + } + else if (joinOp->innerKw) + keywords << "INNER"; + else if (joinOp->crossKw) + keywords << "CROSS"; + + keywords << "JOIN"; + break; + } + case Dialect::Sqlite2: + { + if (joinOp->naturalKw) + keywords << "NATURAL"; + + if (joinOp->leftKw) + keywords << "LEFT"; + else if (joinOp->rightKw) + keywords << "RIGHT"; + else if (joinOp->fullKw) + keywords << "FULL"; + + if (joinOp->innerKw) + keywords << "INNER"; + else if (joinOp->crossKw) + keywords << "CROSS"; + else if (joinOp->outerKw) + keywords << "OUTER"; + + keywords << "JOIN"; + break; + } + } + + if (keywords.size() == 0) + return; + + for (const QString& kw : keywords) + withKeyword(kw); + + if (cfg->SqlEnterpriseFormatter.NlAfterJoinStmt.get()) + withNewLine(); +} + +FormatSelectCoreJoinConstraint::FormatSelectCoreJoinConstraint(SqliteSelect::Core::JoinConstraint* joinConstr) : + joinConstr(joinConstr) +{ +} + +void FormatSelectCoreJoinConstraint::formatInternal() +{ + if (joinConstr->expr) + withKeyword("ON").withStatement(joinConstr->expr, "joinConstr"); + else + withKeyword("USING").withParDefLeft().withIdList(joinConstr->columnNames).withParDefRight(); +} + +FormatSelectCoreJoinSourceOther::FormatSelectCoreJoinSourceOther(SqliteSelect::Core::JoinSourceOther* joinSourceOther) : + joinSourceOther(joinSourceOther) +{ +} + +void FormatSelectCoreJoinSourceOther::formatInternal() +{ + withStatement(joinSourceOther->joinOp).withStatement(joinSourceOther->singleSource).withStatement(joinSourceOther->joinConstraint); +} + + +FormatSelectCoreJoinSource::FormatSelectCoreJoinSource(SqliteSelect::Core::JoinSource* joinSource) : + joinSource(joinSource) +{ +} + +void FormatSelectCoreJoinSource::formatInternal() +{ +// withStatement(joinSource->singleSource).withStatementList(joinSource->otherSources, "otherSources", ListSeparator::NONE); + withStatement(joinSource->singleSource).withStatementList(joinSource->otherSources, QString(), ListSeparator::NONE); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatselect.h b/Plugins/SqlEnterpriseFormatter/formatselect.h new file mode 100644 index 0000000..8cc4f5f --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatselect.h @@ -0,0 +1,103 @@ +#ifndef FORMATSELECT_H +#define FORMATSELECT_H + +#include "formatstatement.h" +#include "parser/ast/sqliteselect.h" + +class FormatSelect : public FormatStatement +{ + public: + FormatSelect(SqliteSelect *select); + + protected: + void formatInternal(); + + private: + SqliteSelect* select = nullptr; +}; + +class FormatSelectCore : public FormatStatement +{ + public: + FormatSelectCore(SqliteSelect::Core *core); + + protected: + void formatInternal(); + + private: + SqliteSelect::Core* core = nullptr; +}; + +class FormatSelectCoreResultColumn : public FormatStatement +{ + public: + FormatSelectCoreResultColumn(SqliteSelect::Core::ResultColumn *resCol); + + protected: + void formatInternal(); + + private: + SqliteSelect::Core::ResultColumn *resCol = nullptr; +}; + +class FormatSelectCoreSingleSource : public FormatStatement +{ + public: + FormatSelectCoreSingleSource(SqliteSelect::Core::SingleSource *singleSource); + + protected: + void formatInternal(); + + private: + SqliteSelect::Core::SingleSource *singleSource = nullptr; +}; + +class FormatSelectCoreJoinOp : public FormatStatement +{ + public: + FormatSelectCoreJoinOp(SqliteSelect::Core::JoinOp *joinOp); + + protected: + void formatInternal(); + + private: + SqliteSelect::Core::JoinOp *joinOp = nullptr; +}; + +class FormatSelectCoreJoinConstraint : public FormatStatement +{ + public: + FormatSelectCoreJoinConstraint(SqliteSelect::Core::JoinConstraint *joinConstr); + + protected: + void formatInternal(); + + private: + SqliteSelect::Core::JoinConstraint *joinConstr = nullptr; +}; + +class FormatSelectCoreJoinSourceOther : public FormatStatement +{ + public: + FormatSelectCoreJoinSourceOther(SqliteSelect::Core::JoinSourceOther *joinSourceOther); + + protected: + void formatInternal(); + + private: + SqliteSelect::Core::JoinSourceOther *joinSourceOther = nullptr; +}; + +class FormatSelectCoreJoinSource : public FormatStatement +{ + public: + FormatSelectCoreJoinSource(SqliteSelect::Core::JoinSource *joinSource); + + protected: + void formatInternal(); + + private: + SqliteSelect::Core::JoinSource *joinSource = nullptr; +}; + +#endif // FORMATSELECT_H diff --git a/Plugins/SqlEnterpriseFormatter/formatstatement.cpp b/Plugins/SqlEnterpriseFormatter/formatstatement.cpp new file mode 100644 index 0000000..04c6aae --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatstatement.cpp @@ -0,0 +1,1064 @@ +#include "formatstatement.h" +#include "formatselect.h" +#include "formatexpr.h" +#include "formatlimit.h" +#include "formatraise.h" +#include "formatwith.h" +#include "formatcreatetable.h" +#include "formatcreatevirtualtable.h" +#include "formatforeignkey.h" +#include "formatcolumntype.h" +#include "formatindexedcolumn.h" +#include "formatinsert.h" +#include "formatempty.h" +#include "formataltertable.h" +#include "formatanalyze.h" +#include "formatattach.h" +#include "formatbegintrans.h" +#include "formatcommittrans.h" +#include "formatcopy.h" +#include "formatcreateindex.h" +#include "formatcreatetrigger.h" +#include "formatdelete.h" +#include "formatupdate.h" +#include "formatdropindex.h" +#include "formatdroptable.h" +#include "formatdroptrigger.h" +#include "formatdropview.h" +#include "formatorderby.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitelimit.h" +#include "parser/ast/sqliteraise.h" +#include "parser/ast/sqlitewith.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteorderby.h" +#include "sqlenterpriseformatter.h" +#include "common/utils_sql.h" +#include "common/global.h" +#include +#include + +#define FORMATTER_FACTORY_ENTRY(query, Type, FormatType) \ + if (dynamic_cast(query)) \ + return new FormatType(dynamic_cast(query)) + +const QString FormatStatement::SPACE = " "; +const QString FormatStatement::NEWLINE = "\n"; +qint64 FormatStatement::nameSeq = 0; + +FormatStatement::FormatStatement() +{ + static_qstring(nameTpl, "statement_%1"); + + indents.push(0); + statementName = nameTpl.arg(QString::number(nameSeq++)); +} + +FormatStatement::~FormatStatement() +{ + cleanup(); +} + +QString FormatStatement::format() +{ + buildTokens(); + return detokenize() + NEWLINE; // extra space when formatting multiple top level (not in CREATE TRIGGER) queries +} + +void FormatStatement::setSelectedWrapper(NameWrapper wrapper) +{ + this->wrapper = wrapper; +} + +void FormatStatement::setConfig(Cfg::SqlEnterpriseFormatterConfig* cfg) +{ + this->cfg = cfg; +} + +void FormatStatement::buildTokens() +{ + cleanup(); + resetInternal(); + formatInternal(); +} + +FormatStatement *FormatStatement::forQuery(SqliteStatement *query) +{ + FormatStatement* stmt = nullptr; + FORMATTER_FACTORY_ENTRY(query, SqliteSelect, FormatSelect); + FORMATTER_FACTORY_ENTRY(query, SqliteSelect::Core, FormatSelectCore); + FORMATTER_FACTORY_ENTRY(query, SqliteSelect::Core::ResultColumn, FormatSelectCoreResultColumn); + FORMATTER_FACTORY_ENTRY(query, SqliteSelect::Core::JoinConstraint, FormatSelectCoreJoinConstraint); + FORMATTER_FACTORY_ENTRY(query, SqliteSelect::Core::JoinOp, FormatSelectCoreJoinOp); + FORMATTER_FACTORY_ENTRY(query, SqliteSelect::Core::JoinSource, FormatSelectCoreJoinSource); + FORMATTER_FACTORY_ENTRY(query, SqliteSelect::Core::JoinSourceOther, FormatSelectCoreJoinSourceOther); + FORMATTER_FACTORY_ENTRY(query, SqliteSelect::Core::SingleSource, FormatSelectCoreSingleSource); + FORMATTER_FACTORY_ENTRY(query, SqliteExpr, FormatExpr); + FORMATTER_FACTORY_ENTRY(query, SqliteWith, FormatWith); + FORMATTER_FACTORY_ENTRY(query, SqliteWith::CommonTableExpression, FormatWithCommonTableExpression); + FORMATTER_FACTORY_ENTRY(query, SqliteRaise, FormatRaise); + FORMATTER_FACTORY_ENTRY(query, SqliteLimit, FormatLimit); + FORMATTER_FACTORY_ENTRY(query, SqliteCreateTable, FormatCreateTable); + FORMATTER_FACTORY_ENTRY(query, SqliteCreateTable::Column, FormatCreateTableColumn); + FORMATTER_FACTORY_ENTRY(query, SqliteCreateTable::Column::Constraint, FormatCreateTableColumnConstraint); + FORMATTER_FACTORY_ENTRY(query, SqliteCreateTable::Constraint, FormatCreateTableConstraint); + FORMATTER_FACTORY_ENTRY(query, SqliteForeignKey, FormatForeignKey); + FORMATTER_FACTORY_ENTRY(query, SqliteForeignKey::Condition, FormatForeignKeyCondition); + FORMATTER_FACTORY_ENTRY(query, SqliteColumnType, FormatColumnType); + FORMATTER_FACTORY_ENTRY(query, SqliteIndexedColumn, FormatIndexedColumn); + FORMATTER_FACTORY_ENTRY(query, SqliteInsert, FormatInsert); + FORMATTER_FACTORY_ENTRY(query, SqliteEmptyQuery, FormatEmpty); + FORMATTER_FACTORY_ENTRY(query, SqliteAlterTable, FormatAlterTable); + FORMATTER_FACTORY_ENTRY(query, SqliteAnalyze, FormatAnalyze); + FORMATTER_FACTORY_ENTRY(query, SqliteAttach, FormatAttach); + FORMATTER_FACTORY_ENTRY(query, SqliteBeginTrans, FormatBeginTrans); + FORMATTER_FACTORY_ENTRY(query, SqliteCommitTrans, FormatCommitTrans); + FORMATTER_FACTORY_ENTRY(query, SqliteCopy, FormatCopy); + FORMATTER_FACTORY_ENTRY(query, SqliteCreateVirtualTable, FormatCreateVirtualTable); + FORMATTER_FACTORY_ENTRY(query, SqliteCreateIndex, FormatCreateIndex); + FORMATTER_FACTORY_ENTRY(query, SqliteCreateTrigger, FormatCreateTrigger); + FORMATTER_FACTORY_ENTRY(query, SqliteCreateTrigger::Event, FormatCreateTriggerEvent); + FORMATTER_FACTORY_ENTRY(query, SqliteUpdate, FormatUpdate); + FORMATTER_FACTORY_ENTRY(query, SqliteDelete, FormatDelete); + FORMATTER_FACTORY_ENTRY(query, SqliteDropIndex, FormatDropIndex); + FORMATTER_FACTORY_ENTRY(query, SqliteDropTable, FormatDropTable); + FORMATTER_FACTORY_ENTRY(query, SqliteDropTrigger, FormatDropTrigger); + FORMATTER_FACTORY_ENTRY(query, SqliteDropView, FormatDropView); + FORMATTER_FACTORY_ENTRY(query, SqliteOrderBy, FormatOrderBy); + + if (stmt) + stmt->dialect = query->dialect; + else if (query) + qWarning() << "Unhandled query passed to enterprise formatter!"; + else + qWarning() << "Null query passed to enterprise formatter!"; + + return stmt; +} + +void FormatStatement::resetInternal() +{ +} + +FormatStatement& FormatStatement::withKeyword(const QString& kw) +{ + withToken(FormatToken::KEYWORD, kw); + return *this; +} + +FormatStatement& FormatStatement::withLinedUpKeyword(const QString& kw, const QString& lineUpName) +{ + withToken(FormatToken::LINED_UP_KEYWORD, kw, getFinalLineUpName(lineUpName)); + return *this; +} + +FormatStatement& FormatStatement::withId(const QString& id) +{ + withToken(FormatToken::ID, id); + return *this; +} + +FormatStatement& FormatStatement::withOperator(const QString& oper) +{ + withToken(FormatToken::OPERATOR, oper); + return *this; +} + +FormatStatement&FormatStatement::withStringOrId(const QString& id) +{ + withToken(FormatToken::STRING_OR_ID, id); + return *this; +} + +FormatStatement& FormatStatement::withIdDot() +{ + withToken(FormatToken::ID_DOT, "."); + return *this; +} + +FormatStatement& FormatStatement::withStar() +{ + withToken(FormatToken::STAR, "*"); + return *this; +} + +FormatStatement& FormatStatement::withFloat(double value) +{ + withToken(FormatToken::FLOAT, value); + return *this; +} + +FormatStatement& FormatStatement::withInteger(qint64 value) +{ + withToken(FormatToken::INTEGER, value); + return *this; +} + +FormatStatement& FormatStatement::withString(const QString& value) +{ + withToken(FormatToken::STRING, value); + return *this; +} + +FormatStatement& FormatStatement::withBlob(const QString& value) +{ + withToken(FormatToken::BLOB, value); + return *this; +} + +FormatStatement& FormatStatement::withBindParam(const QString& name) +{ + withToken(FormatToken::BIND_PARAM, name); + return *this; +} + +FormatStatement& FormatStatement::withParDefLeft() +{ + withToken(FormatToken::PAR_DEF_LEFT, "("); + return *this; +} + +FormatStatement& FormatStatement::withParDefRight() +{ + withToken(FormatToken::PAR_DEF_RIGHT, ")"); + return *this; +} + +FormatStatement& FormatStatement::withParExprLeft() +{ + withToken(FormatToken::PAR_EXPR_LEFT, "("); + return *this; +} + +FormatStatement& FormatStatement::withParExprRight() +{ + withToken(FormatToken::PAR_EXPR_RIGHT, ")"); + return *this; +} + +FormatStatement& FormatStatement::withParFuncLeft() +{ + withToken(FormatToken::PAR_FUNC_LEFT, "("); + return *this; +} + +FormatStatement& FormatStatement::withParFuncRight() +{ + withToken(FormatToken::PAR_FUNC_RIGHT, ")"); + return *this; +} + +FormatStatement& FormatStatement::withSemicolon() +{ + FormatToken* lastRealToken = getLastRealToken(); + if ((lastRealToken && lastRealToken->type != FormatToken::SEMICOLON) || tokens.size() == 0) + withToken(FormatToken::SEMICOLON, ";"); + + return *this; +} + +FormatStatement& FormatStatement::withListComma() +{ + withToken(FormatToken::COMMA_LIST, ","); + return *this; +} + +FormatStatement& FormatStatement::withCommaOper() +{ + withToken(FormatToken::COMMA_OPER, ","); + return *this; +} + +FormatStatement&FormatStatement::withSortOrder(SqliteSortOrder sortOrder) +{ + if (sortOrder != SqliteSortOrder::null) + withKeyword(sqliteSortOrder(sortOrder)); + + return *this; +} + +FormatStatement&FormatStatement::withConflict(SqliteConflictAlgo onConflict) +{ + if (onConflict != SqliteConflictAlgo::null) + withKeyword("ON").withKeyword("CONFLICT").withKeyword(sqliteConflictAlgo(onConflict)); + + return *this; +} + +FormatStatement& FormatStatement::withFuncId(const QString& func) +{ + withToken(FormatToken::FUNC_ID, func); + return *this; +} + +FormatStatement& FormatStatement::withDataType(const QString& dataType) +{ + withToken(FormatToken::DATA_TYPE, dataType); + return *this; +} + +FormatStatement& FormatStatement::withNewLine() +{ + withToken(FormatToken::NEW_LINE, NEWLINE); + return *this; +} + +FormatStatement& FormatStatement::withLiteral(const QVariant& value) +{ + if (value.isNull()) + return *this; + + bool ok; + if (value.userType() == QVariant::Double) + { + value.toDouble(&ok); + if (ok) + { + withFloat(value.toDouble()); + return *this; + } + } + + value.toInt(&ok); + if (ok) + { + withInteger(value.toInt()); + return *this; + } + + QString str = value.toString(); + if (str.startsWith("x'", Qt::CaseInsensitive) && str.endsWith("'")) + { + withBlob(str); + return *this; + } + + withString(str); + return *this; +} + +FormatStatement& FormatStatement::withStatement(SqliteStatement* stmt, const QString& indentName, FormatStatementEnricher enricher) +{ + if (!stmt) + return *this; + + FormatStatement* formatStmt = forQuery(stmt, dialect, wrapper, cfg); + if (!formatStmt) + return *this; + + formatStmt->parentFormatStatement = this; + + if (enricher) + enricher(formatStmt); + + formatStmt->buildTokens(); + formatStmt->deleteTokens = false; + + if (!indentName.isNull()) + markAndKeepIndent(indentName); + + tokens += formatStmt->tokens; + + if (!indentName.isNull()) + withDecrIndent(); + + delete formatStmt; + return *this; +} + +FormatStatement& FormatStatement::markIndent(const QString& name) +{ + withToken(FormatToken::INDENT_MARKER, statementName + "_" + name); + return *this; +} + +FormatStatement& FormatStatement::markAndKeepIndent(const QString& name) +{ + markIndent(name); + withIncrIndent(name); + return *this; +} + +FormatStatement&FormatStatement::withIncrIndent(int newIndent) +{ + withToken(FormatToken::SET_INDENT, newIndent); + return *this; +} + +FormatStatement& FormatStatement::withIncrIndent(const QString& name) +{ + if (name.isNull()) + withToken(FormatToken::INCR_INDENT, name); + else + withToken(FormatToken::INCR_INDENT, statementName + "_" + name); + + return *this; +} + +FormatStatement& FormatStatement::withDecrIndent() +{ + withToken(FormatToken::DECR_INDENT, QString()); + return *this; +} + +FormatStatement&FormatStatement::markKeywordLineUp(const QString& keyword, const QString& lineUpName) +{ + withToken(FormatToken::MARK_KEYWORD_LINEUP, getFinalLineUpName(lineUpName), keyword.length()); + return *this; +} + +FormatStatement&FormatStatement::withSeparator(FormatStatement::ListSeparator sep) +{ + switch (sep) + { + case ListSeparator::COMMA: + withListComma(); + break; + case ListSeparator::EXPR_COMMA: + withCommaOper(); + break; + case ListSeparator::NEW_LINE: + withNewLine(); + break; + case ListSeparator::SEMICOLON: + withSemicolon(); + break; + case ListSeparator::NONE: + break; + } + return *this; +} + +FormatStatement& FormatStatement::withIdList(const QStringList& names, const QString& indentName, ListSeparator sep) +{ + if (!indentName.isNull()) + markAndKeepIndent(indentName); + + bool first = true; + foreach (const QString& name, names) + { + if (!first) + withSeparator(sep); + + withId(name); + first = false; + } + + if (!indentName.isNull()) + withDecrIndent(); + + return *this; +} + +void FormatStatement::withToken(FormatStatement::FormatToken::Type type, const QVariant& value, const QVariant& additionalValue) +{ + FormatToken* token = new FormatToken; + token->type = type; + token->value = value; + token->additionalValue = additionalValue; + tokens << token; +} + +void FormatStatement::cleanup() +{ + kwLineUpPosition.clear(); + line = ""; + lines.clear(); + namedIndents.clear(); + resetIndents(); + if (deleteTokens) + { + for (FormatToken* token : tokens) + delete token; + } + + tokens.clear(); +} + +int FormatStatement::getLineUpValue(const QString& lineUpName) +{ + if (kwLineUpPosition.contains(lineUpName)) + return kwLineUpPosition[lineUpName]; + + return 0; +} + +QString FormatStatement::detokenize() +{ + bool uppercaseKeywords = cfg->SqlEnterpriseFormatter.UppercaseKeywords.get(); + + for (FormatToken* token : tokens) + { + applySpace(token->type); + switch (token->type) + { + case FormatToken::LINED_UP_KEYWORD: + { + if (cfg->SqlEnterpriseFormatter.LineUpKeywords.get()) + { + QString kw = token->value.toString(); + QString lineUpName = token->additionalValue.toString(); + int lineUpValue = getLineUpValue(lineUpName); + + int indentLength = lineUpValue - kw.length(); + if (indentLength > 0) + line += SPACE.repeated(indentLength); + + line += uppercaseKeywords ? kw.toUpper() : kw.toLower(); + + break; + } + else + { + // No 'break', so we go to next case, the regular KEYWORD + } + } + case FormatToken::KEYWORD: + { + applyIndent(); + line += uppercaseKeywords ? token->value.toString().toUpper() : token->value.toString().toLower(); + break; + } + case FormatToken::FUNC_ID: + case FormatToken::DATA_TYPE: + { + applyIndent(); + line += wrapObjIfNeeded(token->value.toString(), dialect, wrapper); + break; + } + case FormatToken::ID: + { + applyIndent(); + formatId(token->value.toString()); + break; + } + case FormatToken::STRING_OR_ID: + { + applyIndent(); + QString val = token->value.toString(); + if (val.contains("\"")) + formatId(token->value.toString()); + else + line += wrapObjName(token->value.toString(), NameWrapper::DOUBLE_QUOTE); + + break; + } + case FormatToken::STAR: + case FormatToken::FLOAT: + case FormatToken::INTEGER: + case FormatToken::BLOB: + case FormatToken::BIND_PARAM: + case FormatToken::STRING: + { + applyIndent(); + line += token->value.toString(); + break; + } + case FormatToken::OPERATOR: + { + bool spaceAdded = endsWithSpace() || applyIndent(); + if (cfg->SqlEnterpriseFormatter.SpaceBeforeMathOp.get() && !spaceAdded) + line += SPACE; + + line += token->value.toString(); + if (cfg->SqlEnterpriseFormatter.SpaceAfterMathOp.get()) + line += SPACE; + + break; + } + case FormatToken::ID_DOT: + { + bool spaceAdded = endsWithSpace() || applyIndent(); + if (cfg->SqlEnterpriseFormatter.SpaceBeforeDot.get() && !spaceAdded) + line += SPACE; + + line += token->value.toString(); + if (cfg->SqlEnterpriseFormatter.SpaceAfterDot.get()) + line += SPACE; + + break; + } + case FormatToken::PAR_DEF_LEFT: + { + bool spaceBefore = cfg->SqlEnterpriseFormatter.SpaceBeforeOpenPar.get(); + bool spaceAfter = cfg->SqlEnterpriseFormatter.SpaceAfterOpenPar.get(); + bool nlBefore = cfg->SqlEnterpriseFormatter.NlBeforeOpenParDef.get(); + bool nlAfter = cfg->SqlEnterpriseFormatter.NlAfterOpenParDef.get(); + detokenizeLeftPar(token, spaceBefore, spaceAfter, nlBefore, nlAfter); + break; + } + case FormatToken::PAR_DEF_RIGHT: + { + bool spaceBefore = cfg->SqlEnterpriseFormatter.SpaceBeforeClosePar.get(); + bool spaceAfter = cfg->SqlEnterpriseFormatter.SpaceAfterClosePar.get(); + bool nlBefore = cfg->SqlEnterpriseFormatter.NlBeforeCloseParDef.get(); + bool nlAfter = cfg->SqlEnterpriseFormatter.NlAfterCloseParDef.get(); + detokenizeRightPar(token, spaceBefore, spaceAfter, nlBefore, nlAfter); + break; + } + case FormatToken::PAR_EXPR_LEFT: + { + bool spaceBefore = cfg->SqlEnterpriseFormatter.SpaceBeforeOpenPar.get(); + bool spaceAfter = cfg->SqlEnterpriseFormatter.SpaceAfterOpenPar.get(); + bool nlBefore = cfg->SqlEnterpriseFormatter.NlBeforeOpenParExpr.get(); + bool nlAfter = cfg->SqlEnterpriseFormatter.NlAfterOpenParExpr.get(); + detokenizeLeftPar(token, spaceBefore, spaceAfter, nlBefore, nlAfter); + break; + } + case FormatToken::PAR_EXPR_RIGHT: + { + bool spaceBefore = cfg->SqlEnterpriseFormatter.SpaceBeforeClosePar.get(); + bool spaceAfter = cfg->SqlEnterpriseFormatter.SpaceAfterClosePar.get(); + bool nlBefore = cfg->SqlEnterpriseFormatter.NlBeforeCloseParExpr.get(); + bool nlAfter = cfg->SqlEnterpriseFormatter.NlAfterCloseParExpr.get(); + detokenizeRightPar(token, spaceBefore, spaceAfter, nlBefore, nlAfter); + break; + } + case FormatToken::PAR_FUNC_LEFT: + { + bool spaceBefore = cfg->SqlEnterpriseFormatter.SpaceBeforeOpenPar.get() && !cfg->SqlEnterpriseFormatter.NoSpaceAfterFunctionName.get(); + bool spaceAfter = cfg->SqlEnterpriseFormatter.SpaceAfterOpenPar.get(); + bool nlBefore = cfg->SqlEnterpriseFormatter.NlBeforeOpenParExpr.get(); + bool nlAfter = cfg->SqlEnterpriseFormatter.NlAfterOpenParExpr.get(); + detokenizeLeftPar(token, spaceBefore, spaceAfter, nlBefore, nlAfter); + break; + } + case FormatToken::PAR_FUNC_RIGHT: + { + bool spaceBefore = cfg->SqlEnterpriseFormatter.SpaceBeforeClosePar.get(); + bool spaceAfter = cfg->SqlEnterpriseFormatter.SpaceAfterClosePar.get(); + bool nlBefore = cfg->SqlEnterpriseFormatter.NlBeforeCloseParExpr.get(); + bool nlAfter = cfg->SqlEnterpriseFormatter.NlAfterCloseParExpr.get(); + detokenizeRightPar(token, spaceBefore, spaceAfter, nlBefore, nlAfter); + break; + } + case FormatToken::SEMICOLON: + { + if (cfg->SqlEnterpriseFormatter.SpaceNeverBeforeSemicolon.get()) + { + removeAllSpaces(); + } + else + { + bool spaceAdded = endsWithSpace() || applyIndent(); + if (cfg->SqlEnterpriseFormatter.SpaceBeforeMathOp.get() && !spaceAdded) + line += SPACE; + } + + line += token->value.toString(); + if (cfg->SqlEnterpriseFormatter.NlAfterSemicolon.get()) + newLine(); + else if (cfg->SqlEnterpriseFormatter.SpaceAfterMathOp.get()) + line += SPACE; + + break; + } + case FormatToken::COMMA_LIST: + { + if (cfg->SqlEnterpriseFormatter.SpaceNeverBeforeComma.get()) + { + removeAllSpaces(); + } + else + { + bool spaceAdded = endsWithSpace() || applyIndent(); + if (cfg->SqlEnterpriseFormatter.SpaceBeforeCommaInList.get() && !spaceAdded) + line += SPACE; + } + + line += token->value.toString(); + if (cfg->SqlEnterpriseFormatter.NlAfterComma.get()) + newLine(); + else if (cfg->SqlEnterpriseFormatter.SpaceAfterCommaInList.get()) + line += SPACE; + + break; + } + case FormatToken::COMMA_OPER: + { + if (cfg->SqlEnterpriseFormatter.SpaceNeverBeforeComma.get()) + { + removeAllSpaces(); + } + else + { + bool spaceAdded = endsWithSpace() || applyIndent(); + if (cfg->SqlEnterpriseFormatter.SpaceBeforeCommaInList.get() && !spaceAdded) + line += SPACE; + } + + line += token->value.toString(); + if (cfg->SqlEnterpriseFormatter.NlAfterCommaInExpr.get()) + newLine(); + else if (cfg->SqlEnterpriseFormatter.SpaceAfterCommaInList.get()) + line += SPACE; + + break; + } + case FormatToken::NEW_LINE: + { + newLine(); + break; + } + case FormatToken::INDENT_MARKER: + { + QString indentName = token->value.toString(); + namedIndents[indentName] = predictCurrentIndent(token); + break; + } + case FormatToken::INCR_INDENT: + { + if (!token->value.isNull()) + incrIndent(token->value.toString()); + else + incrIndent(); + + break; + } + case FormatToken::SET_INDENT: + { + setIndent(indents.top() + token->value.toInt()); + break; + } + case FormatToken::DECR_INDENT: + { + decrIndent(); + break; + } + case FormatToken::MARK_KEYWORD_LINEUP: + { + QString lineUpName = token->value.toString(); + int lineUpLength = predictCurrentIndent(token) + token->additionalValue.toInt(); + if (!kwLineUpPosition.contains(lineUpName) || lineUpLength > kwLineUpPosition[lineUpName]) + kwLineUpPosition[lineUpName] = lineUpLength; + + break; + } + } + updateLastToken(token); + } + newLine(); + return lines.join(NEWLINE); +} + +bool FormatStatement::applyIndent() +{ + int indentToAdd = indents.top() - line.length(); + if (indentToAdd <= 0) + return false; + + line += SPACE.repeated(indentToAdd); + return true; +} + +void FormatStatement::applySpace(FormatToken::Type type) +{ + if (lastToken && isSpaceExpectingType(type) && isSpaceExpectingType(lastToken->type) && !endsWithSpace()) + line += SPACE; +} + +bool FormatStatement::isSpaceExpectingType(FormatStatement::FormatToken::Type type) +{ + switch (type) + { + case FormatToken::KEYWORD: + case FormatToken::LINED_UP_KEYWORD: + case FormatToken::ID: + case FormatToken::STRING_OR_ID: + case FormatToken::FLOAT: + case FormatToken::STRING: + case FormatToken::INTEGER: + case FormatToken::BLOB: + case FormatToken::BIND_PARAM: + case FormatToken::FUNC_ID: + case FormatToken::DATA_TYPE: + case FormatToken::STAR: + return true; + case FormatToken::OPERATOR: + case FormatToken::ID_DOT: + case FormatToken::PAR_DEF_LEFT: + case FormatToken::PAR_DEF_RIGHT: + case FormatToken::PAR_EXPR_LEFT: + case FormatToken::PAR_EXPR_RIGHT: + case FormatToken::PAR_FUNC_LEFT: + case FormatToken::PAR_FUNC_RIGHT: + case FormatToken::SEMICOLON: + case FormatToken::COMMA_LIST: + case FormatToken::COMMA_OPER: + case FormatToken::NEW_LINE: + case FormatToken::INDENT_MARKER: + case FormatToken::INCR_INDENT: + case FormatToken::DECR_INDENT: + case FormatToken::SET_INDENT: + case FormatToken::MARK_KEYWORD_LINEUP: + break; + } + return false; +} + +bool FormatStatement::isMetaType(FormatStatement::FormatToken::Type type) +{ + switch (type) + { + case FormatToken::INDENT_MARKER: + case FormatToken::INCR_INDENT: + case FormatToken::DECR_INDENT: + case FormatToken::SET_INDENT: + case FormatToken::MARK_KEYWORD_LINEUP: + return true; + case FormatToken::KEYWORD: + case FormatToken::LINED_UP_KEYWORD: + case FormatToken::ID: + case FormatToken::STRING_OR_ID: + case FormatToken::FLOAT: + case FormatToken::STRING: + case FormatToken::INTEGER: + case FormatToken::BLOB: + case FormatToken::BIND_PARAM: + case FormatToken::FUNC_ID: + case FormatToken::DATA_TYPE: + case FormatToken::OPERATOR: + case FormatToken::STAR: + case FormatToken::ID_DOT: + case FormatToken::PAR_DEF_LEFT: + case FormatToken::PAR_DEF_RIGHT: + case FormatToken::PAR_EXPR_LEFT: + case FormatToken::PAR_EXPR_RIGHT: + case FormatToken::PAR_FUNC_LEFT: + case FormatToken::PAR_FUNC_RIGHT: + case FormatToken::SEMICOLON: + case FormatToken::COMMA_LIST: + case FormatToken::COMMA_OPER: + case FormatToken::NEW_LINE: + break; + } + return false; +} + +void FormatStatement::newLine() +{ + if (line.length() == 0) // prevents double new-line when for example "))" occurs and it has new-line before and after + return; + + lines << line; + line = ""; +} + +void FormatStatement::incrIndent(const QString& name) +{ + if (!name.isNull()) + { + if (namedIndents.contains(name)) + { + indents.push(namedIndents[name]); + } + else + { + indents.push(indents.top() + cfg->SqlEnterpriseFormatter.TabSize.get()); + qCritical() << __func__ << "No named indent found:" << name; + } + } + else + indents.push(indents.top() + cfg->SqlEnterpriseFormatter.TabSize.get()); +} + +void FormatStatement::decrIndent() +{ + if (indents.size() <= 1) + return; + + indents.pop(); +} + +void FormatStatement::setIndent(int newIndent) +{ + indents.push(newIndent); +} + +bool FormatStatement::endsWithSpace() +{ + return line.length() == 0 || line[line.length() - 1].isSpace(); +} + +FormatStatement::FormatToken* FormatStatement::getLastRealToken(bool skipNewLines) +{ + for (FormatToken* tk : reverse(tokens)) + { + if (!isMetaType(tk->type) && (!skipNewLines || tk->type != FormatToken::NEW_LINE)) + return tk; + } + return nullptr; +} + +void FormatStatement::detokenizeLeftPar(FormatToken* token, bool spaceBefore, bool spaceAfter, bool nlBefore, bool nlAfter) +{ + bool spaceAdded = endsWithSpace(); + if (nlBefore) + { + newLine(); + spaceAdded = true; + } + + spaceAdded |= applyIndent(); + if (spaceBefore && !spaceAdded) + line += SPACE; + + line += token->value.toString(); + if (nlAfter) + { + newLine(); + if (cfg->SqlEnterpriseFormatter.IndentParenthesisBlock.get()) + incrIndent(); + } + else if (spaceAfter) + line += SPACE; +} + +void FormatStatement::detokenizeRightPar(FormatStatement::FormatToken* token, bool spaceBefore, bool spaceAfter, bool nlBefore, bool nlAfter) +{ + bool spaceAdded = endsWithSpace(); + if (nlBefore) + { + newLine(); + spaceAdded = true; + if (cfg->SqlEnterpriseFormatter.IndentParenthesisBlock.get()) + decrIndent(); + } + + spaceAdded |= applyIndent(); + if (spaceBefore && !spaceAdded) + line += SPACE; + + line += token->value.toString(); + if (nlAfter) + newLine(); + else if (spaceAfter) + line += SPACE; +} + +void FormatStatement::resetIndents() +{ + indents.clear(); + indents.push(0); +} + +void FormatStatement::removeAllSpaces() +{ + removeAllSpacesFromLine(); + while (endsWithSpace() && lines.size() > 0) + { + line = lines.takeLast(); + removeAllSpacesFromLine(); + + if (lines.size() == 0) + break; + } +} + +void FormatStatement::removeAllSpacesFromLine() +{ + while (endsWithSpace() && line.length() > 0) + line.chop(1); +} + +void FormatStatement::updateLastToken(FormatStatement::FormatToken* token) +{ + if (!isMetaType(token->type)) + lastToken = token; +} + +QString FormatStatement::getFinalLineUpName(const QString& lineUpName) +{ + QString finalName = statementName; + if (!lineUpName.isNull()) + finalName += "_" + lineUpName; + + return finalName; +} + +int FormatStatement::predictCurrentIndent(FormatToken* currentMetaToken) +{ + QString lineBackup = line; + bool isSpace = applyIndent() || endsWithSpace(); + + if (!isSpace) + { + // We haven't added any space and there is no space currently at the end of line. + // We need to predict if next real (printable) token will require space to be added. + // If yes, we add it virtually here, so we know the indent required afterwards. + // First we need to find next real token: + int tokenIdx = tokens.indexOf(currentMetaToken); + FormatToken* nextRealToken = nullptr; + for (FormatToken* tk : tokens.mid(tokenIdx + 1)) + { + if (!isMetaType(tk->type)) + { + nextRealToken = tk; + break; + } + } + + // If the real token was found we can see if it will require additional space for indent: + if ((nextRealToken && isSpaceExpectingType(lastToken->type) && isSpaceExpectingType(nextRealToken->type)) || willStartWithNewLine(nextRealToken)) + { + // Next real token does not start with new line, but it does require additional space: + line += SPACE; + } + } + + int result = line.length(); + line = lineBackup; + return result; +} + +bool FormatStatement::willStartWithNewLine(FormatStatement::FormatToken* token) +{ + return (token->type == FormatToken::PAR_DEF_LEFT && cfg->SqlEnterpriseFormatter.NlBeforeOpenParDef) || + (token->type == FormatToken::PAR_EXPR_LEFT && cfg->SqlEnterpriseFormatter.NlBeforeOpenParExpr) || + (token->type == FormatToken::PAR_FUNC_LEFT && cfg->SqlEnterpriseFormatter.NlBeforeOpenParExpr) || + (token->type == FormatToken::PAR_DEF_RIGHT && cfg->SqlEnterpriseFormatter.NlBeforeCloseParDef) || + (token->type == FormatToken::PAR_EXPR_RIGHT && cfg->SqlEnterpriseFormatter.NlBeforeCloseParExpr) || + (token->type == FormatToken::PAR_FUNC_RIGHT && cfg->SqlEnterpriseFormatter.NlBeforeCloseParExpr) || + (token->type == FormatToken::NEW_LINE); +} + +void FormatStatement::formatId(const QString& value) +{ + if (cfg->SqlEnterpriseFormatter.AlwaysUseNameWrapping.get()) + line += wrapObjName(value, dialect, wrapper); + else + line += wrapObjIfNeeded(value, dialect, wrapper); +} + +FormatStatement* FormatStatement::forQuery(SqliteStatement* query, Dialect dialect, NameWrapper wrapper, Cfg::SqlEnterpriseFormatterConfig* cfg) +{ + FormatStatement* formatStmt = forQuery(query); + if (formatStmt) + { + formatStmt->dialect = dialect; + formatStmt->wrapper = wrapper; + formatStmt->cfg = cfg; + } + return formatStmt; +} diff --git a/Plugins/SqlEnterpriseFormatter/formatstatement.h b/Plugins/SqlEnterpriseFormatter/formatstatement.h new file mode 100644 index 0000000..1702a3d --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatstatement.h @@ -0,0 +1,198 @@ +#ifndef FORMATSTATEMENT_H +#define FORMATSTATEMENT_H + +#include "parser/ast/sqlitestatement.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "common/utils_sql.h" +#include "sqlenterpriseformatter.h" +#include +#include +#include +#include +#include +#include + +class FormatStatement +{ + public: + enum class ListSeparator + { + NONE, + COMMA, + EXPR_COMMA, + NEW_LINE, + SEMICOLON + }; + + typedef std::function FormatStatementEnricher; + + FormatStatement(); + virtual ~FormatStatement(); + + QString format(); + void setSelectedWrapper(NameWrapper wrapper); + void setConfig(Cfg::SqlEnterpriseFormatterConfig* cfg); + + static FormatStatement* forQuery(SqliteStatement *query); + + FormatStatement& withKeyword(const QString& kw); + FormatStatement& withLinedUpKeyword(const QString& kw, const QString& lineUpName = QString()); + FormatStatement& withId(const QString& id); + FormatStatement& withIdList(const QStringList& names, const QString& indentName = QString(), ListSeparator sep = ListSeparator::COMMA); + FormatStatement& withOperator(const QString& oper); + FormatStatement& withStringOrId(const QString& id); + FormatStatement& withIdDot(); + FormatStatement& withStar(); + FormatStatement& withFloat(double value); + FormatStatement& withInteger(qint64 value); + FormatStatement& withString(const QString& value); + FormatStatement& withBlob(const QString& value); + FormatStatement& withBindParam(const QString& name); + FormatStatement& withParDefLeft(); + FormatStatement& withParDefRight(); + FormatStatement& withParExprLeft(); + FormatStatement& withParExprRight(); + FormatStatement& withParFuncLeft(); + FormatStatement& withParFuncRight(); + FormatStatement& withSemicolon(); + FormatStatement& withListComma(); + FormatStatement& withCommaOper(); + FormatStatement& withSortOrder(SqliteSortOrder sortOrder); + FormatStatement& withConflict(SqliteConflictAlgo onConflict); + FormatStatement& withFuncId(const QString& func); + FormatStatement& withDataType(const QString& dataType); + FormatStatement& withNewLine(); + FormatStatement& withLiteral(const QVariant& value); + FormatStatement& withStatement(SqliteStatement* stmt, const QString& indentName = QString(), FormatStatementEnricher enricher = nullptr); + FormatStatement& markIndent(const QString& name); + FormatStatement& markAndKeepIndent(const QString& name); + FormatStatement& withIncrIndent(int newIndent); + FormatStatement& withIncrIndent(const QString& name = QString()); + FormatStatement& withDecrIndent(); + FormatStatement& markKeywordLineUp(const QString& keyword, const QString& lineUpName = QString()); + FormatStatement& withSeparator(ListSeparator sep); + + template + FormatStatement& withStatementList(QList stmtList, const QString& indentName = QString(), ListSeparator sep = ListSeparator::COMMA, + FormatStatementEnricher enricher = nullptr) + { + if (!indentName.isNull()) + markAndKeepIndent(indentName); + + bool first = true; + foreach (T* stmt, stmtList) + { + if (!first) + withSeparator(sep); + + withStatement(stmt, QString(), enricher); + first = false; + } + + if (!indentName.isNull()) + withDecrIndent(); + + return *this; + } + + template + T* getFormatStatement(SqliteStatement* stmt) + { + return dynamic_cast(forQuery(stmt, dialect, wrapper, cfg)); + } + + protected: + virtual void formatInternal() = 0; + virtual void resetInternal(); + + Dialect dialect = Dialect::Sqlite3; + NameWrapper wrapper = NameWrapper::BRACKET; + Cfg::SqlEnterpriseFormatterConfig* cfg = nullptr; + + private: + struct FormatToken + { + enum Type + { + KEYWORD, + LINED_UP_KEYWORD, + ID, + STRING_OR_ID, + OPERATOR, + STAR, + FLOAT, + STRING, + INTEGER, + BLOB, + BIND_PARAM, + ID_DOT, + PAR_DEF_LEFT, + PAR_DEF_RIGHT, + PAR_EXPR_LEFT, + PAR_EXPR_RIGHT, + PAR_FUNC_LEFT, + PAR_FUNC_RIGHT, + SEMICOLON, + COMMA_LIST, + COMMA_OPER, // for example in LIMIT + FUNC_ID, + DATA_TYPE, + NEW_LINE, + INDENT_MARKER, + INCR_INDENT, + SET_INDENT, + DECR_INDENT, + MARK_KEYWORD_LINEUP + }; + + Type type; + QVariant value; + QVariant additionalValue; + }; + + void withToken(FormatToken::Type type, const QVariant& value, const QVariant& additionalValue = QVariant()); + void cleanup(); + void buildTokens(); + bool applyIndent(); + void applySpace(FormatToken::Type type); + bool isSpaceExpectingType(FormatToken::Type type); + bool isMetaType(FormatToken::Type type); + void newLine(); + void incrIndent(const QString& name = QString()); + void decrIndent(); + void setIndent(int newIndent); + bool endsWithSpace(); + FormatToken* getLastRealToken(bool skipNewLines = false); + QString detokenize(); + void detokenizeLeftPar(FormatToken* token, bool spaceBefore, bool spaceAfter, bool nlBefore, bool nlAfter); + void detokenizeRightPar(FormatToken* token, bool spaceBefore, bool spaceAfter, bool nlBefore, bool nlAfter); + void resetIndents(); + void removeAllSpaces(); + void removeAllSpacesFromLine(); + void updateLastToken(FormatToken* token); + QString getFinalLineUpName(const QString& lineUpName); + int predictCurrentIndent(FormatToken* currentMetaToken); + bool willStartWithNewLine(FormatToken* token); + void formatId(const QString& value); + int getLineUpValue(const QString& lineUpName); + + static FormatStatement* forQuery(SqliteStatement *query, Dialect dialect, NameWrapper wrapper, Cfg::SqlEnterpriseFormatterConfig* cfg); + + QHash kwLineUpPosition; + QHash namedIndents; + QStack indents; + QList tokens; + bool deleteTokens = true; + QStringList lines; + QString line; + FormatToken* lastToken = nullptr; + QString statementName; + FormatStatement* parentFormatStatement = nullptr; + + static qint64 nameSeq; + static const QString SPACE; + static const QString NEWLINE; +}; + +#endif // FORMATSTATEMENT_H diff --git a/Plugins/SqlEnterpriseFormatter/formatupdate.cpp b/Plugins/SqlEnterpriseFormatter/formatupdate.cpp new file mode 100644 index 0000000..ffc3911 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatupdate.cpp @@ -0,0 +1,47 @@ +#include "formatupdate.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqliteexpr.h" +#include "formatwith.h" + +FormatUpdate::FormatUpdate(SqliteUpdate* upd) : + upd(upd) +{ +} + +void FormatUpdate::formatInternal() +{ + if (upd->with) + withStatement(upd->with); + + markKeywordLineUp("UPDATE"); + withKeyword("UPDATE"); + if (upd->onConflict != SqliteConflictAlgo::null) + withKeyword("OR").withKeyword(sqliteConflictAlgo(upd->onConflict)); + + if (!upd->database.isNull()) + withId(upd->database).withIdDot(); + + withId(upd->table); + + if (upd->indexedByKw) + withKeyword("INDEXED").withKeyword("BY").withId(upd->indexedBy); + else if (upd->notIndexedKw) + withKeyword("NOT").withKeyword("INDEXED"); + + withNewLine().withLinedUpKeyword("SET"); + + bool first = true; + foreach (const SqliteUpdate::ColumnAndValue& keyVal, upd->keyValueMap) + { + if (!first) + withListComma(); + + withId(keyVal.first).withOperator("=").withStatement(keyVal.second); + first = false; + } + + if (upd->where) + withNewLine().withLinedUpKeyword("WHERE").withStatement(upd->where); + + withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatupdate.h b/Plugins/SqlEnterpriseFormatter/formatupdate.h new file mode 100644 index 0000000..ca5ee11 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatupdate.h @@ -0,0 +1,20 @@ +#ifndef FORMATUPDATE_H +#define FORMATUPDATE_H + +#include "formatstatement.h" + +class SqliteUpdate; + +class FormatUpdate : public FormatStatement +{ + public: + FormatUpdate(SqliteUpdate* upd); + + protected: + void formatInternal(); + + private: + SqliteUpdate* upd = nullptr; +}; + +#endif // FORMATUPDATE_H diff --git a/Plugins/SqlEnterpriseFormatter/formatvacuum.cpp b/Plugins/SqlEnterpriseFormatter/formatvacuum.cpp new file mode 100644 index 0000000..29b95e4 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatvacuum.cpp @@ -0,0 +1,11 @@ +#include "formatvacuum.h" + +FormatVacuum::FormatVacuum(SqliteVacuum* vacuum) : + vacuum(vacuum) +{ +} + +void FormatVacuum::formatInternal() +{ + withKeyword("VACUUM").withSemicolon(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatvacuum.h b/Plugins/SqlEnterpriseFormatter/formatvacuum.h new file mode 100644 index 0000000..34eedc9 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatvacuum.h @@ -0,0 +1,20 @@ +#ifndef FORMATVACUUM_H +#define FORMATVACUUM_H + +#include "formatstatement.h" + +class SqliteVacuum; + +class FormatVacuum : public FormatStatement +{ + public: + FormatVacuum(SqliteVacuum* vacuum); + + protected: + void formatInternal(); + + private: + SqliteVacuum* vacuum = nullptr; +}; + +#endif // FORMATVACUUM_H diff --git a/Plugins/SqlEnterpriseFormatter/formatwith.cpp b/Plugins/SqlEnterpriseFormatter/formatwith.cpp new file mode 100644 index 0000000..d3d99d5 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatwith.cpp @@ -0,0 +1,38 @@ +#include "formatwith.h" +#include "parser/ast/sqliteselect.h" + +FormatWith::FormatWith(SqliteWith *with) : + with(with) +{ +} + +void FormatWith::setLineUpKeyword(const QString& kw) +{ + lineUpKeyword = kw; +} + +void FormatWith::formatInternal() +{ + markKeywordLineUp(lineUpKeyword); + + withLinedUpKeyword("WITH"); + if (with->recursive) + withKeyword("RECURSIVE"); + + withStatementList(with->cteList); +} + + +FormatWithCommonTableExpression::FormatWithCommonTableExpression(SqliteWith::CommonTableExpression *cte) : + cte(cte) +{ +} + +void FormatWithCommonTableExpression::formatInternal() +{ + withId(cte->table); + if (cte->indexedColumns.size() > 0) + withParDefLeft().withStatementList(cte->indexedColumns, "idxCols").withParDefRight(); + + withKeyword("AS").withParDefLeft().withStatement(cte->select).withParDefRight(); +} diff --git a/Plugins/SqlEnterpriseFormatter/formatwith.h b/Plugins/SqlEnterpriseFormatter/formatwith.h new file mode 100644 index 0000000..618b73b --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/formatwith.h @@ -0,0 +1,31 @@ +#ifndef FORMATWITH_H +#define FORMATWITH_H + +#include "formatstatement.h" +#include "parser/ast/sqlitewith.h" + +class FormatWith : public FormatStatement +{ + public: + FormatWith(SqliteWith* with); + + void setLineUpKeyword(const QString& kw); + void formatInternal(); + + private: + SqliteWith *with = nullptr; + QString lineUpKeyword; +}; + +class FormatWithCommonTableExpression : public FormatStatement +{ + public: + FormatWithCommonTableExpression(SqliteWith::CommonTableExpression* cte); + + void formatInternal(); + + private: + SqliteWith::CommonTableExpression* cte = nullptr; +}; + +#endif // FORMATWITH_H diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp new file mode 100644 index 0000000..8f75960 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp @@ -0,0 +1,112 @@ +#include "sqlenterpriseformatter.h" +#include "formatstatement.h" +#include "common/unused.h" +#include "common/global.h" +#include +#include + +SqlEnterpriseFormatter::SqlEnterpriseFormatter() +{ +} + +QString SqlEnterpriseFormatter::format(SqliteQueryPtr query) +{ + int wrapperIdx = cfg.SqlEnterpriseFormatter.Wrappers.get().indexOf(cfg.SqlEnterpriseFormatter.PrefferedWrapper.get()); + + NameWrapper wrapper = getAllNameWrappers()[wrapperIdx]; + + FormatStatement *formatStmt = FormatStatement::forQuery(query.data()); + if (!formatStmt) + return query->detokenize(); + + formatStmt->setSelectedWrapper(wrapper); + formatStmt->setConfig(&cfg); + QString formatted = formatStmt->format(); + delete formatStmt; + + return formatted; +} + +bool SqlEnterpriseFormatter::init() +{ + Q_INIT_RESOURCE(sqlenterpriseformatter); + + static_qstring(query1, "SELECT (2 + 4) AND (3 + 5), 4 NOT IN (SELECT t1.'some[_]name' + t2.[some'name2] FROM xyz t1 JOIN zxc t2 ON (t1.aaa = t2.aaa)) " + "FROM a, (SELECT id FROM table2);"); + static_qstring(query2, "INSERT INTO table1 (id, value1, value2) VALUES (1, (2 + 5), (SELECT id FROM table2));"); + static_qstring(query3, "CREATE TABLE tab (id INTEGER PRIMARY KEY, value1 VARCHAR(6), value2 NUMBER(8,2) NOT NULL DEFAULT 1.0);"); + static_qstring(query4, "CREATE UNIQUE INDEX IF NOT EXISTS dbName.idx1 ON [messages column] (id COLLATE x ASC, lang DESC, description);"); + + Parser parser(Dialect::Sqlite3); + + for (const QString& q : {query1, query2, query3, query4}) + { + if (!parser.parse(q)) + { + qWarning() << "SqlEnterpriseFormatter preview query parsing error:" << parser.getErrorString(); + continue; + } + previewQueries << parser.getQueries().first(); + } + + updatePreview(); + + return GenericPlugin::init(); +} + +void SqlEnterpriseFormatter::deinit() +{ + Q_CLEANUP_RESOURCE(sqlenterpriseformatter); +} + + +void SqlEnterpriseFormatter::updatePreview() +{ + QStringList output; + for (const SqliteQueryPtr& q : previewQueries) + output << format(q); + + cfg.SqlEnterpriseFormatter.PreviewCode.set(output.join("\n\n")); +} + +void SqlEnterpriseFormatter::configModified(CfgEntry* entry) +{ + if (entry == &cfg.SqlEnterpriseFormatter.PreviewCode) + return; + + updatePreview(); +} + +QString Cfg::getNameWrapperStr(NameWrapper wrapper) +{ + return wrapObjName(QObject::tr("name", "example name wrapper"), wrapper); +} + +QStringList Cfg::getNameWrapperStrings() +{ + QStringList strings; + for (NameWrapper nw : getAllNameWrappers()) + strings << wrapObjName(QObject::tr("name", "example name wrapper"), nw); + + return strings; +} + +CfgMain* SqlEnterpriseFormatter::getMainUiConfig() +{ + return &cfg; +} + +QString SqlEnterpriseFormatter::getConfigUiForm() const +{ + return "SqlEnterpriseFormatter"; +} + +void SqlEnterpriseFormatter::configDialogOpen() +{ + connect(&cfg.SqlEnterpriseFormatter, SIGNAL(changed(CfgEntry*)), this, SLOT(configModified(CfgEntry*))); +} + +void SqlEnterpriseFormatter::configDialogClosed() +{ + disconnect(&cfg.SqlEnterpriseFormatter, SIGNAL(changed(CfgEntry*)), this, SLOT(configModified(CfgEntry*))); +} diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h new file mode 100644 index 0000000..2701745 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h @@ -0,0 +1,84 @@ +#ifndef SQLENTERPRISEFORMATTER_H +#define SQLENTERPRISEFORMATTER_H + +#include "sqlenterpriseformatter_global.h" +#include "plugins/genericplugin.h" +#include "plugins/sqlformatterplugin.h" +#include "config_builder.h" +#include "common/utils_sql.h" +#include "plugins/uiconfiguredplugin.h" +#include "parser/ast/sqlitequery.h" + +namespace Cfg +{ + QString getNameWrapperStr(NameWrapper wrapper); + QStringList getNameWrapperStrings(); +} + +CFG_CATEGORIES(SqlEnterpriseFormatterConfig, + CFG_CATEGORY(SqlEnterpriseFormatter, + CFG_ENTRY(int, TabSize, 4) + CFG_ENTRY(bool, LineUpKeywords, true) + CFG_ENTRY(bool, IndentParenthesisBlock, true) + CFG_ENTRY(bool, NlBeforeOpenParDef, false) + CFG_ENTRY(bool, NlAfterOpenParDef, true) + CFG_ENTRY(bool, NlBeforeCloseParDef, true) + CFG_ENTRY(bool, NlAfterCloseParDef, true) + CFG_ENTRY(bool, NlBeforeOpenParExpr, false) + CFG_ENTRY(bool, NlAfterOpenParExpr, false) + CFG_ENTRY(bool, NlBeforeCloseParExpr, false) + CFG_ENTRY(bool, NlAfterCloseParExpr, false) + CFG_ENTRY(bool, NlAfterJoinStmt, true) + CFG_ENTRY(bool, NlAfterComma, true) + CFG_ENTRY(bool, NlAfterCommaInExpr, false) + CFG_ENTRY(bool, NlAfterSemicolon, true) + CFG_ENTRY(bool, NlNeverBeforeSemicolon, true) + CFG_ENTRY(bool, NlBetweenConstraints, true) + CFG_ENTRY(bool, SpaceBeforeCommaInList, false) + CFG_ENTRY(bool, SpaceAfterCommaInList, true) + CFG_ENTRY(bool, SpaceBeforeOpenPar, true) + CFG_ENTRY(bool, SpaceAfterOpenPar, false) + CFG_ENTRY(bool, SpaceBeforeClosePar, false) + CFG_ENTRY(bool, SpaceAfterClosePar, true) + CFG_ENTRY(bool, SpaceBeforeDot, false) + CFG_ENTRY(bool, SpaceAfterDot, false) + CFG_ENTRY(bool, SpaceBeforeMathOp, true) + CFG_ENTRY(bool, SpaceAfterMathOp, true) + CFG_ENTRY(bool, NoSpaceAfterFunctionName, true) + CFG_ENTRY(bool, SpaceNeverBeforeSemicolon, true) + CFG_ENTRY(bool, SpaceNeverBeforeComma, true) + CFG_ENTRY(bool, UppercaseKeywords, true) + CFG_ENTRY(bool, UppercaseDataTypes, true) + CFG_ENTRY(bool, AlwaysUseNameWrapping, false) + CFG_ENTRY(QString, PrefferedWrapper, getNameWrapperStr(NameWrapper::BRACKET)) + CFG_ENTRY(QStringList, Wrappers, getNameWrapperStrings(), false) + CFG_ENTRY(QString, PreviewCode, QString(), false) + ) +) + +class SQLENTERPRISEFORMATTERSHARED_EXPORT SqlEnterpriseFormatter : public GenericPlugin, public SqlFormatterPlugin, public UiConfiguredPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("sqlenterpriseformatter.json") + + public: + SqlEnterpriseFormatter(); + + QString format(SqliteQueryPtr query); + bool init(); + void deinit(); + CfgMain* getMainUiConfig(); + QString getConfigUiForm() const; + void configDialogOpen(); + void configDialogClosed(); + + private: + QList previewQueries; + CFG_LOCAL_PERSISTABLE(SqlEnterpriseFormatterConfig, cfg) + + private slots: + void updatePreview(); + void configModified(CfgEntry* entry); +}; + +#endif // SQLENTERPRISEFORMATTER_H diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.json b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.json new file mode 100644 index 0000000..2e6f543 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.json @@ -0,0 +1,7 @@ +{ + "type": "CodeFormatterPlugin", + "title": "SQL Enterprise", + "description": "Advanced SQL formatter.", + "version": 10002, + "author": "SalSoft" +} diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.qrc b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.qrc new file mode 100644 index 0000000..bd23092 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.qrc @@ -0,0 +1,5 @@ + + + sqlenterpriseformatter.ui + + diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui new file mode 100644 index 0000000..49e8788 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui @@ -0,0 +1,671 @@ + + + SqlEnterpriseFormatter + + + + 0 + 0 + 612 + 464 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + 1 + + + + 0 + + + + Indentation + + + + 4 + + + 4 + + + + + Line up keywords in multi-line queries + + + SqlEnterpriseFormatter.LineUpKeywords + + + + + + + Indent contents of parenthesis block + + + SqlEnterpriseFormatter.IndentParenthesisBlock + + + + + + + Qt::Vertical + + + + 20 + 187 + + + + + + + + Tab size: + + + + + + + SqlEnterpriseFormatter.TabSize + + + + + + + + New lines + + + + 4 + + + 4 + + + + + #scrollArea { background: transparent; } + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + true + + + + + 0 + 0 + 578 + 350 + + + + #scrollAreaWidgetContents { background: transparent; } + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Before opening parenthesis in column definitions + + + SqlEnterpriseFormatter.NlBeforeOpenParDef + + + + + + + After opening parenthesis in column definitions + + + SqlEnterpriseFormatter.NlAfterOpenParDef + + + + + + + Before closing parenthesis in column definitions + + + SqlEnterpriseFormatter.NlBeforeCloseParDef + + + + + + + After closing parenthesis in column definitions + + + SqlEnterpriseFormatter.NlAfterCloseParDef + + + + + + + Before opening parenthesis in expressions + + + SqlEnterpriseFormatter.NlBeforeOpenParExpr + + + + + + + After opening parenthesis in expressions + + + SqlEnterpriseFormatter.NlAfterOpenParExpr + + + + + + + Before closing parenthesis in expressions + + + SqlEnterpriseFormatter.NlBeforeCloseParExpr + + + + + + + After closing parenthesis in expressions + + + SqlEnterpriseFormatter.NlAfterCloseParExpr + + + + + + + After *JOIN keywords in FROM clause + + + SqlEnterpriseFormatter.NlAfterJoinStmt + + + + + + + Put each column constraint in CREATE TABLE into new line + + + SqlEnterpriseFormatter.NlBetweenConstraints + + + + + + + After comma + + + SqlEnterpriseFormatter.NlAfterComma + + + + + + + After comma in expressions + + + SqlEnterpriseFormatter.NlAfterCommaInExpr + + + + + + + After semicolon + + + SqlEnterpriseFormatter.NlAfterSemicolon + + + + + + + Never before semicolon + + + SqlEnterpriseFormatter.NlNeverBeforeSemicolon + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + widget_2 + scrollArea + + + + White spaces + + + + 4 + + + 4 + + + + + #scrollArea_2 { background: transparent; } + + + QFrame::NoFrame + + + QFrame::Plain + + + 10 + + + true + + + + + 0 + 0 + 424 + 325 + + + + #scrollAreaWidgetContents_2 { background: transparent; } + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Before comma in lists + + + SqlEnterpriseFormatter.SpaceBeforeCommaInList + + + + + + + After comma in lists + + + SqlEnterpriseFormatter.SpaceAfterCommaInList + + + + + + + Before opening parenthesis + + + SqlEnterpriseFormatter.SpaceBeforeOpenPar + + + + + + + After opening parenthesis + + + SqlEnterpriseFormatter.SpaceAfterOpenPar + + + + + + + Before closing parenthesis + + + SqlEnterpriseFormatter.SpaceBeforeClosePar + + + + + + + After closing parenthesis + + + SqlEnterpriseFormatter.SpaceAfterClosePar + + + + + + + No space between SQL function name and opening parenthesis + + + SqlEnterpriseFormatter.NoSpaceAfterFunctionName + + + + + + + Before dot operator (in path to database object) + + + SqlEnterpriseFormatter.SpaceBeforeDot + + + + + + + After dot operator (in path to database object) + + + SqlEnterpriseFormatter.SpaceAfterDot + + + + + + + Before mathematical operator + + + SqlEnterpriseFormatter.SpaceBeforeMathOp + + + + + + + After mathematical operator + + + SqlEnterpriseFormatter.SpaceAfterMathOp + + + + + + + Never before comma + + + SqlEnterpriseFormatter.SpaceNeverBeforeComma + + + + + + + Never before semicolon + + + SqlEnterpriseFormatter.SpaceNeverBeforeSemicolon + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + Names + + + + 4 + + + 4 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Preferred name wrapper + + + + + + + SqlEnterpriseFormatter.PrefferedWrapper + + + SqlEnterpriseFormatter.Wrappers + + + + + + + Always use name wrapping + + + SqlEnterpriseFormatter.AlwaysUseNameWrapping + + + + + + + Uppercase data type names + + + SqlEnterpriseFormatter.UppercaseDataTypes + + + + + + + Uppercase keywords + + + SqlEnterpriseFormatter.UppercaseKeywords + + + + + + + + + + 0 + 2 + + + + Preview + + + + + + SqlEnterpriseFormatter.PreviewCode + + + true + + + + + + + + + + + + ConfigComboBox + QComboBox +
common/configcombobox.h
+
+ + SqlView + QPlainTextEdit +
sqlview.h
+
+
+ + +
diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter_global.h b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter_global.h new file mode 100644 index 0000000..e600191 --- /dev/null +++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter_global.h @@ -0,0 +1,12 @@ +#ifndef SQLENTERPRISEFORMATTER_GLOBAL_H +#define SQLENTERPRISEFORMATTER_GLOBAL_H + +#include + +#if defined(SQLENTERPRISEFORMATTER_LIBRARY) +# define SQLENTERPRISEFORMATTERSHARED_EXPORT Q_DECL_EXPORT +#else +# define SQLENTERPRISEFORMATTERSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // SQLENTERPRISEFORMATTER_GLOBAL_H diff --git a/Plugins/SqlExport/SqlExport.pro b/Plugins/SqlExport/SqlExport.pro new file mode 100644 index 0000000..29a952b --- /dev/null +++ b/Plugins/SqlExport/SqlExport.pro @@ -0,0 +1,29 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-04-03T18:21:00 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = SqlExport +TEMPLATE = lib + +DEFINES += SQLEXPORT_LIBRARY + +SOURCES += sqlexport.cpp + +HEADERS += sqlexport.h\ + sqlexport_global.h + +FORMS += \ + SqlExportQuery.ui \ + SqlExportCommon.ui + +OTHER_FILES += \ + sqlexport.json + +RESOURCES += \ + sqlexport.qrc diff --git a/Plugins/SqlExport/SqlExportCommon.ui b/Plugins/SqlExport/SqlExportCommon.ui new file mode 100644 index 0000000..9b5de21 --- /dev/null +++ b/Plugins/SqlExport/SqlExportCommon.ui @@ -0,0 +1,51 @@ + + + sqlExportCommonConfig + + + + 0 + 0 + 467 + 86 + + + + Form + + + + + + Generate "DROP IF EXISTS" statement before "CREATE" statement + + + SqlExport.GenerateDrop + + + + + + + Format DDL statements only (excludes "INSERT" statements) + + + SqlExport.FormatDdlsOnly + + + + + + + Use SQL formatter to format exported SQL statements + + + SqlExport.UseFormatter + + + + + + + + diff --git a/Plugins/SqlExport/SqlExportQuery.ui b/Plugins/SqlExport/SqlExportQuery.ui new file mode 100644 index 0000000..4d2a6ae --- /dev/null +++ b/Plugins/SqlExport/SqlExportQuery.ui @@ -0,0 +1,85 @@ + + + sqlExportQueryConfig + + + + 0 + 0 + 467 + 163 + + + + Form + + + + + + Use SQL formatter to format exported SQL statements + + + SqlExport.UseFormatter + + + + + + + Table name to use for insert statements: + + + + + + + Generate "CREATE TABLE" statement at the begining + + + SqlExport.GenerateCreateTable + + + + + + + Include the query in comments + + + SqlExport.IncludeQueryInComments + + + + + + + Generate "DROP IF EXISTS" statement before "CREATE" statement + + + SqlExport.GenerateDrop + + + + + + + SqlExport.QueryTable + + + + + + + Format DDL statements only (excludes "INSERT" statements) + + + SqlExport.FormatDdlsOnly + + + + + + + + diff --git a/Plugins/SqlExport/sqlexport.cpp b/Plugins/SqlExport/sqlexport.cpp new file mode 100644 index 0000000..0a379b1 --- /dev/null +++ b/Plugins/SqlExport/sqlexport.cpp @@ -0,0 +1,329 @@ +#include "sqlexport.h" +#include "common/utils_sql.h" +#include "sqlitestudio.h" +#include "config_builder.h" +#include "services/exportmanager.h" +#include "common/unused.h" +#include "services/codeformatter.h" +#include + +SqlExport::SqlExport() +{ +} + +QString SqlExport::getFormatName() const +{ + return "SQL"; +} + +ExportManager::StandardConfigFlags SqlExport::standardOptionsToEnable() const +{ + return ExportManager::CODEC; +} + +CfgMain* SqlExport::getConfig() +{ + return &cfg; +} + +QString SqlExport::defaultFileExtension() const +{ + return "sql"; +} + +QString SqlExport::getExportConfigFormName() const +{ + if (exportMode == ExportManager::QUERY_RESULTS) + return "sqlExportQueryConfig"; + + return "sqlExportCommonConfig"; +} + +bool SqlExport::beforeExportQueryResults(const QString& query, QList& columns, const QHash providedData) +{ + UNUSED(providedData); + static_qstring(dropDdl, "DROP TABLE IF EXISTS %1;"); + + Dialect dialect = db->getDialect(); + QStringList colDefs; + for (QueryExecutor::ResultColumnPtr resCol : columns) + colDefs << wrapObjIfNeeded(resCol->displayName, dialect); + + this->columns = colDefs.join(", "); + + writeHeader(); + if (cfg.SqlExport.IncludeQueryInComments.get()) + { + writeln(tr("-- Results of query:")); + writeln(commentAllSqlLines(query)); + writeln("--"); + } + + writeBegin(); + + if (!cfg.SqlExport.GenerateCreateTable.get()) + return true; + + theTable = wrapObjIfNeeded(cfg.SqlExport.QueryTable.get(), dialect); + QString ddl = "CREATE TABLE " + theTable + " (" + this->columns + ");"; + writeln(""); + + if (cfg.SqlExport.GenerateDrop.get()) + writeln(formatQuery(dropDdl.arg(theTable))); + + writeln(formatQuery(ddl)); + return true; +} + +bool SqlExport::exportQueryResultsRow(SqlResultsRowPtr row) +{ + QStringList argList = rowToArgList(row); + QString argStr = argList.join(", "); + QString sql = "INSERT INTO " + theTable + " (" + this->columns + ") VALUES (" + argStr + ");"; + writeln(sql); + return true; +} + +bool SqlExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, const QHash providedData) +{ + UNUSED(createTable); + UNUSED(providedData); + return exportTable(database, table, columnNames, ddl); +} + +bool SqlExport::exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, const QHash providedData) +{ + UNUSED(createTable); + UNUSED(providedData); + return exportTable(database, table, columnNames, ddl); +} + +bool SqlExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl) +{ + static_qstring(dropDdl, "DROP TABLE IF EXISTS %1;"); + + Dialect dialect = db->getDialect(); + + QStringList colList; + for (const QString& colName : columnNames) + colList << wrapObjIfNeeded(colName, dialect); + + columns = colList.join(", "); + + if (isTableExport()) + { + writeHeader(); + writeFkDisable(); + writeBegin(); + } + + QString fullName = getNameForObject(database, table, false); + writeln(""); + writeln(tr("-- Table: %1").arg(fullName)); + + theTable = getNameForObject(database, table, true, dialect); + + if (cfg.SqlExport.GenerateDrop.get()) + writeln(formatQuery(dropDdl.arg(theTable))); + + writeln(formatQuery(ddl)); + return true; +} + +bool SqlExport::exportTableRow(SqlResultsRowPtr data) +{ + QStringList argList = rowToArgList(data); + QString argStr = argList.join(", "); + QString sql = "INSERT INTO " + theTable + " (" + columns + ") VALUES (" + argStr + ");"; + if (!cfg.SqlExport.FormatDdlsOnly.get()) + sql = formatQuery(sql); + + writeln(sql); + return true; +} + +bool SqlExport::afterExport() +{ + writeCommit(); + return true; +} + +bool SqlExport::beforeExportDatabase(const QString& database) +{ + UNUSED(database); + writeHeader(); + writeFkDisable(); + writeBegin(); + return true; +} + +bool SqlExport::exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) +{ + UNUSED(createIndex); + static_qstring(dropDdl, "DROP INDEX IF EXISTS %1;"); + + QString index = getNameForObject(database, name, false); + writeln(""); + writeln(tr("-- Index: %1").arg(index)); + + QString fullName = getNameForObject(database, name, true, db->getDialect()); + if (cfg.SqlExport.GenerateDrop.get()) + writeln(formatQuery(dropDdl.arg(fullName))); + + writeln(formatQuery(ddl)); + return true; +} + +bool SqlExport::exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) +{ + UNUSED(createTrigger); + static_qstring(dropDdl, "DROP TRIGGER IF EXISTS %1;"); + + QString trig = getNameForObject(database, name, false); + writeln(""); + writeln(tr("-- Trigger: %1").arg(trig)); + + QString fullName = getNameForObject(database, name, true, db->getDialect()); + if (cfg.SqlExport.GenerateDrop.get()) + writeln(dropDdl.arg(fullName)); + + writeln(formatQuery(formatQuery(ddl))); + return true; +} + +bool SqlExport::exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr createView) +{ + UNUSED(createView); + static_qstring(dropDdl, "DROP VIEW IF EXISTS %1;"); + + QString view = getNameForObject(database, name, false); + writeln(""); + writeln(tr("-- View: %1").arg(view)); + + QString fullName = getNameForObject(database, name, true, db->getDialect()); + if (cfg.SqlExport.GenerateDrop.get()) + writeln(dropDdl.arg(fullName)); + + writeln(formatQuery(formatQuery(ddl))); + return true; +} + +void SqlExport::writeHeader() +{ + QDateTime ctime = QDateTime::currentDateTime(); + writeln("--"); + writeln(tr("-- File generated with SQLiteStudio v%1 on %2").arg(SQLITESTUDIO->getVersionString()).arg(ctime.toString())); + writeln("--"); + if (standardOptionsToEnable().testFlag(ExportManager::CODEC)) + { + writeln(tr("-- Text encoding used: %1").arg(QString::fromLatin1(codec->name()))); + writeln("--"); + } +} + +void SqlExport::writeBegin() +{ + writeln("BEGIN TRANSACTION;"); +} + +void SqlExport::writeCommit() +{ + writeln(""); + writeln("COMMIT TRANSACTION;"); +} + +void SqlExport::writeFkDisable() +{ + writeln("PRAGMA foreign_keys = off;"); +} + +QString SqlExport::formatQuery(const QString& sql) +{ + if (cfg.SqlExport.UseFormatter.get()) + return FORMATTER->format("sql", sql, db); + + return sql; +} + +QString SqlExport::getNameForObject(const QString& database, const QString& name, bool wrapped, Dialect dialect) +{ + QString obj = wrapped ? wrapObjIfNeeded(name, dialect) : name; + if (!database.isNull() && database.toLower() != "main") + obj = (wrapped ? wrapObjIfNeeded(database, dialect) : database) + "." + obj; + + return obj; +} + +QStringList SqlExport::rowToArgList(SqlResultsRowPtr row) +{ + QStringList argList; + for (const QVariant& value : row->valueList()) + { + if (!value.isValid() || value.isNull()) + { + argList << "NULL"; + continue; + } + + switch (value.userType()) + { + case QVariant::Int: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + argList << value.toString(); + break; + case QVariant::Double: + argList << QString::number(value.toDouble()); + break; + case QVariant::Bool: + argList << QString::number(value.toInt()); + break; + case QVariant::ByteArray: + { + if (db->getVersion() >= 3) // version 2 will go to the regular string processing + { + argList << "X'" + value.toByteArray().toHex().toUpper() + "'"; + break; + } + } + default: + argList << wrapString(escapeString(value.toString())); + break; + } + } + return argList; +} + +void SqlExport::validateOptions() +{ + if (exportMode == ExportManager::QUERY_RESULTS) + { + bool valid = !cfg.SqlExport.QueryTable.get().isEmpty(); + EXPORT_MANAGER->handleValidationFromPlugin(valid, cfg.SqlExport.QueryTable, tr("Table name for INSERT statements is mandatory.")); + } + + bool useFormatter = cfg.SqlExport.UseFormatter.get(); + EXPORT_MANAGER->updateVisibilityAndEnabled(cfg.SqlExport.FormatDdlsOnly, true, useFormatter); + if (!useFormatter) + cfg.SqlExport.FormatDdlsOnly.set(false); + + if (exportMode == ExportManager::QUERY_RESULTS) + { + bool generateCreate = cfg.SqlExport.GenerateCreateTable.get(); + EXPORT_MANAGER->updateVisibilityAndEnabled(cfg.SqlExport.GenerateDrop, true, generateCreate); + if (!generateCreate) + cfg.SqlExport.GenerateDrop.set(false); + } +} + +bool SqlExport::init() +{ + Q_INIT_RESOURCE(sqlexport); + return GenericExportPlugin::init(); +} + +void SqlExport::deinit() +{ + Q_CLEANUP_RESOURCE(sqlexport); +} diff --git a/Plugins/SqlExport/sqlexport.h b/Plugins/SqlExport/sqlexport.h new file mode 100644 index 0000000..9e23418 --- /dev/null +++ b/Plugins/SqlExport/sqlexport.h @@ -0,0 +1,65 @@ +#ifndef SQLEXPORT_H +#define SQLEXPORT_H + +#include "plugins/genericexportplugin.h" +#include "sqlexport_global.h" +#include "config_builder.h" + +CFG_CATEGORIES(SqlExportConfig, + CFG_CATEGORY(SqlExport, + CFG_ENTRY(QString, QueryTable, QString::null) + CFG_ENTRY(bool, GenerateCreateTable, false) + CFG_ENTRY(bool, IncludeQueryInComments, true) + CFG_ENTRY(bool, UseFormatter, false) + CFG_ENTRY(bool, FormatDdlsOnly, false) + CFG_ENTRY(bool, GenerateDrop, false) + ) +) + +class SQLEXPORTSHARED_EXPORT SqlExport : public GenericExportPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN("sqlexport.json") + + public: + SqlExport(); + + QString getFormatName() const; + ExportManager::StandardConfigFlags standardOptionsToEnable() const; + CfgMain* getConfig(); + QString defaultFileExtension() const; + QString getExportConfigFormName() const; + bool beforeExportQueryResults(const QString& query, QList& columns, + const QHash providedData); + bool exportQueryResultsRow(SqlResultsRowPtr row); + bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash providedData); + bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, + const QHash providedData); + bool exportTableRow(SqlResultsRowPtr data); + bool afterExport(); + bool beforeExportDatabase(const QString& database); + bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex); + bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger); + bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr createView); + void validateOptions(); + bool init(); + void deinit(); + + private: + bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl); + void writeHeader(); + void writeBegin(); + void writeCommit(); + void writeFkDisable(); + QString formatQuery(const QString& sql); + QString getNameForObject(const QString& database, const QString& name, bool wrapped, Dialect dialect = Dialect::Sqlite3); + QStringList rowToArgList(SqlResultsRowPtr row); + + QString theTable; + QString columns; + CFG_LOCAL(SqlExportConfig, cfg) +}; + +#endif // SQLEXPORT_H diff --git a/Plugins/SqlExport/sqlexport.json b/Plugins/SqlExport/sqlexport.json new file mode 100644 index 0000000..bd06adf --- /dev/null +++ b/Plugins/SqlExport/sqlexport.json @@ -0,0 +1,7 @@ +{ + "type": "ExportPlugin", + "title": "SQL export", + "description": "Provides SQL format for exporting", + "version": 10100, + "author": "SalSoft" +} diff --git a/Plugins/SqlExport/sqlexport.qrc b/Plugins/SqlExport/sqlexport.qrc new file mode 100644 index 0000000..447617f --- /dev/null +++ b/Plugins/SqlExport/sqlexport.qrc @@ -0,0 +1,6 @@ + + + SqlExportQuery.ui + SqlExportCommon.ui + + diff --git a/Plugins/SqlExport/sqlexport_global.h b/Plugins/SqlExport/sqlexport_global.h new file mode 100644 index 0000000..f7c05a8 --- /dev/null +++ b/Plugins/SqlExport/sqlexport_global.h @@ -0,0 +1,12 @@ +#ifndef SQLEXPORT_GLOBAL_H +#define SQLEXPORT_GLOBAL_H + +#include + +#if defined(SQLEXPORT_LIBRARY) +# define SQLEXPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define SQLEXPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // SQLEXPORT_GLOBAL_H diff --git a/Plugins/SqlFormatterSimple/SqlFormatterSimple.pro b/Plugins/SqlFormatterSimple/SqlFormatterSimple.pro new file mode 100644 index 0000000..2c60801 --- /dev/null +++ b/Plugins/SqlFormatterSimple/SqlFormatterSimple.pro @@ -0,0 +1,28 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-12-02T16:14:12 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = SqlFormatterSimple +TEMPLATE = lib + +DEFINES += SQLFORMATTERSIMPLE_LIBRARY + +SOURCES += sqlformattersimpleplugin.cpp + +HEADERS += sqlformattersimpleplugin.h\ + sqlformattersimple_global.h + +FORMS += \ + SqlFormatterSimple.ui + +OTHER_FILES += \ + sqlformattersimple.json + +RESOURCES += \ + sqlformattersimple.qrc diff --git a/Plugins/SqlFormatterSimple/SqlFormatterSimple.ui b/Plugins/SqlFormatterSimple/SqlFormatterSimple.ui new file mode 100644 index 0000000..7e81ee0 --- /dev/null +++ b/Plugins/SqlFormatterSimple/SqlFormatterSimple.ui @@ -0,0 +1,51 @@ + + + SqlFormatterSimplePlugin + + + + 0 + 0 + 354 + 290 + + + + + + + Upper case keywords + + + SqlFormatterSimple.UpperCaseKeywords + + + + + + + Reduce multiple whitespaces to single whitespace + + + SqlFormatterSimple.TrimLongSpaces + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/Plugins/SqlFormatterSimple/sqlformattersimple.json b/Plugins/SqlFormatterSimple/sqlformattersimple.json new file mode 100644 index 0000000..ff51f13 --- /dev/null +++ b/Plugins/SqlFormatterSimple/sqlformattersimple.json @@ -0,0 +1,7 @@ +{ + "type": "CodeFormatterPlugin", + "title": "SQL Simple", + "description": "Basic formatter with very little options.", + "version": 10000, + "author": "SalSoft" +} diff --git a/Plugins/SqlFormatterSimple/sqlformattersimple.qrc b/Plugins/SqlFormatterSimple/sqlformattersimple.qrc new file mode 100644 index 0000000..febfbd2 --- /dev/null +++ b/Plugins/SqlFormatterSimple/sqlformattersimple.qrc @@ -0,0 +1,5 @@ + + + SqlFormatterSimple.ui + + diff --git a/Plugins/SqlFormatterSimple/sqlformattersimple_global.h b/Plugins/SqlFormatterSimple/sqlformattersimple_global.h new file mode 100644 index 0000000..926a1b0 --- /dev/null +++ b/Plugins/SqlFormatterSimple/sqlformattersimple_global.h @@ -0,0 +1,12 @@ +#ifndef SQLFORMATTERSIMPLE_GLOBAL_H +#define SQLFORMATTERSIMPLE_GLOBAL_H + +#include + +#if defined(SQLFORMATTERSIMPLE_LIBRARY) +# define SQLFORMATTERSIMPLESHARED_EXPORT Q_DECL_EXPORT +#else +# define SQLFORMATTERSIMPLESHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // SQLFORMATTERSIMPLE_GLOBAL_H diff --git a/Plugins/SqlFormatterSimple/sqlformattersimpleplugin.cpp b/Plugins/SqlFormatterSimple/sqlformattersimpleplugin.cpp new file mode 100644 index 0000000..0cb60c5 --- /dev/null +++ b/Plugins/SqlFormatterSimple/sqlformattersimpleplugin.cpp @@ -0,0 +1,50 @@ +#include "sqlformattersimpleplugin.h" + +SqlFormatterSimplePlugin::SqlFormatterSimplePlugin() +{ +} + +QString SqlFormatterSimplePlugin::format(SqliteQueryPtr query) +{ + TokenList tokens = query->tokens; + foreach (TokenPtr token, tokens) + { + if (token->type == Token::KEYWORD && cfg.SqlFormatterSimple.UpperCaseKeywords.get()) + token->value = token->value.toUpper(); + + if (token->type == Token::SPACE && cfg.SqlFormatterSimple.TrimLongSpaces.get() && + token->value.length() > 1) + token->value = " "; + } + + return tokens.detokenize(); +} + +bool SqlFormatterSimplePlugin::init() +{ + Q_INIT_RESOURCE(sqlformattersimple); + return GenericPlugin::init(); +} + +void SqlFormatterSimplePlugin::deinit() +{ + Q_CLEANUP_RESOURCE(sqlformattersimple); +} + +QString SqlFormatterSimplePlugin::getConfigUiForm() const +{ + return "SqlFormatterSimplePlugin"; +} + +CfgMain* SqlFormatterSimplePlugin::getMainUiConfig() +{ + return &cfg; +} + +void SqlFormatterSimplePlugin::configDialogOpen() +{ +} + +void SqlFormatterSimplePlugin::configDialogClosed() +{ +} diff --git a/Plugins/SqlFormatterSimple/sqlformattersimpleplugin.h b/Plugins/SqlFormatterSimple/sqlformattersimpleplugin.h new file mode 100644 index 0000000..dde25c8 --- /dev/null +++ b/Plugins/SqlFormatterSimple/sqlformattersimpleplugin.h @@ -0,0 +1,38 @@ +#ifndef SQLFORMATTERSIMPLEPLUGIN_H +#define SQLFORMATTERSIMPLEPLUGIN_H + +#include "sqlformattersimple_global.h" +#include "plugins/sqlformatterplugin.h" +#include "config_builder.h" +#include "plugins/genericplugin.h" +#include "plugins/uiconfiguredplugin.h" +#include + +CFG_CATEGORIES(SqlFormatterSimpleConfig, + CFG_CATEGORY(SqlFormatterSimple, + CFG_ENTRY(bool, UpperCaseKeywords, true) + CFG_ENTRY(bool, TrimLongSpaces, true) + ) +) + +class SQLFORMATTERSIMPLESHARED_EXPORT SqlFormatterSimplePlugin : public GenericPlugin, public SqlFormatterPlugin, public UiConfiguredPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("sqlformattersimple.json") + + public: + SqlFormatterSimplePlugin(); + + QString format(SqliteQueryPtr query); + bool init(); + void deinit(); + QString getConfigUiForm() const; + CfgMain* getMainUiConfig(); + void configDialogOpen(); + void configDialogClosed(); + + private: + CFG_LOCAL_PERSISTABLE(SqlFormatterSimpleConfig, cfg) +}; + +#endif // SQLFORMATTERSIMPLEPLUGIN_H diff --git a/Plugins/XmlExport/XmlExport.pro b/Plugins/XmlExport/XmlExport.pro new file mode 100644 index 0000000..87e0c7f --- /dev/null +++ b/Plugins/XmlExport/XmlExport.pro @@ -0,0 +1,27 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-06-15T05:20:57 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +QT -= gui + +TARGET = XmlExport +TEMPLATE = lib + +DEFINES += XMLEXPORT_LIBRARY + +SOURCES += xmlexport.cpp + +HEADERS += xmlexport.h\ + xmlexport_global.h + +FORMS += XmlExport.ui + +OTHER_FILES += \ + xmlexport.json + +RESOURCES += \ + xmlexport.qrc diff --git a/Plugins/XmlExport/XmlExport.ui b/Plugins/XmlExport/XmlExport.ui new file mode 100644 index 0000000..2a236c6 --- /dev/null +++ b/Plugins/XmlExport/XmlExport.ui @@ -0,0 +1,151 @@ + + + XmlExportConfig + + + + 0 + 0 + 325 + 280 + + + + Form + + + + + + Output format + + + + + + Format document (new lines, indentation) + + + true + + + format + + + XmlExport.Format + + + + + + + Compress (everything in one line) + + + compress + + + XmlExport.Format + + + + + + + + + + Special characters escaping + + + + + + <p>Ampersands will be used for shorter values and CDATA will be used for larger values. This applies only to values that require character escaping. Other values will be exported as they are.</p> + + + Use CDATA and ampersands + + + true + + + mixed + + + XmlExport.Escaping + + + + + + + <p>Every value requiring character escepe will be enclosed in CDATA block.</p> + + + Always use CDATA + + + cdata + + + XmlExport.Escaping + + + + + + + <p>Every character that require esceping will be replaced with its ampersand escape sequence. No CDATA blocks will be used.</p> + + + Always use ampersand + + + ampersand + + + XmlExport.Escaping + + + + + + + + + + Define XML namespace + + + true + + + false + + + XmlExport.UseNamespace + + + + + + XmlExport.Namespace + + + + + + + + + + + ConfigRadioButton + QRadioButton +
common/configradiobutton.h
+
+
+ + +
diff --git a/Plugins/XmlExport/xmlexport.cpp b/Plugins/XmlExport/xmlexport.cpp new file mode 100644 index 0000000..746d246 --- /dev/null +++ b/Plugins/XmlExport/xmlexport.cpp @@ -0,0 +1,468 @@ +#include "xmlexport.h" +#include "services/exportmanager.h" +#include "common/unused.h" +#include + +const QString XmlExport::docBegin = QStringLiteral("\n"); + +XmlExport::XmlExport() +{ +} + +QString XmlExport::getFormatName() const +{ + return QStringLiteral("XML"); +} + +ExportManager::StandardConfigFlags XmlExport::standardOptionsToEnable() const +{ + return ExportManager::CODEC; +} + +QString XmlExport::getExportConfigFormName() const +{ + return QStringLiteral("XmlExportConfig"); +} + +CfgMain* XmlExport::getConfig() +{ + return &cfg; +} + +void XmlExport::validateOptions() +{ + bool useNs = cfg.XmlExport.UseNamespace.get(); + EXPORT_MANAGER->updateVisibilityAndEnabled(cfg.XmlExport.Namespace, true, useNs); + + bool nsValid = !useNs || !cfg.XmlExport.Namespace.get().isEmpty(); + EXPORT_MANAGER->handleValidationFromPlugin(nsValid, cfg.XmlExport.Namespace, tr("Enter the namespace to use (for example: http://my.namespace.org")); +} + +QString XmlExport::defaultFileExtension() const +{ + return QStringLiteral("xml"); +} + +bool XmlExport::beforeExportQueryResults(const QString& query, QList& columns, const QHash providedData) +{ + UNUSED(providedData); + + setupConfig(); + + write(docBegin.arg(codecName)); + + writeln(QString("").arg(nsStr)); + incrIndent(); + + writeln(""); + incrIndent(); + writeln(escape(query)); + decrIndent(); + writeln(""); + + QList columnTypes = QueryExecutor::resolveColumnTypes(db, columns, true); + writeln(""); + incrIndent(); + int i = 0; + DataType type; + for (QueryExecutor::ResultColumnPtr col : columns) + { + type = columnTypes[i]; + + writeln(""); + incrIndent(); + writeln(""+ escape(col->displayName) + ""); + writeln(""+ escape(col->column) + ""); + writeln(""+ escape(col->table) + "
"); + writeln(""+ escape(col->database) + ""); + writeln(""+ escape(type.toFullTypeString()) + ""); + decrIndent(); + writeln("
"); + i++; + } + decrIndent(); + writeln("
"); + + writeln(""); + incrIndent(); + return true; +} + +bool XmlExport::exportQueryResultsRow(SqlResultsRowPtr row) +{ + static const QString rowTpl = QStringLiteral("%2"); + static const QString nullTpl = QStringLiteral(""); + + writeln(""); + incrIndent(); + + int i = 0; + for (const QVariant& value : row->valueList()) + { + if (value.isNull()) + writeln(nullTpl.arg(i)); + else + writeln(rowTpl.arg(i).arg(escape(value.toString()))); + } + + decrIndent(); + writeln(""); + return true; +} + +bool XmlExport::afterExportQueryResults() +{ + decrIndent(); + write(""); + decrIndent(); + write(""); + return true; +} + +bool XmlExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, const QHash providedData) +{ + UNUSED(columnNames); + UNUSED(providedData); + if (isTableExport()) + { + setupConfig(); + write(docBegin.arg(codecName)); + } + + writeln(QString("").arg(isTableExport() ? nsStr : "")); + incrIndent(); + + writeln("" + escape(database) + ""); + writeln("" + escape(table) + ""); + if (!createTable->withOutRowId.isNull()) + writeln(QString("true")); + + writeln("" + escape(ddl) + ""); + + writeln(""); + incrIndent(); + for (SqliteCreateTable::Column* col : createTable->columns) + { + writeln(""); + incrIndent(); + writeln(""+ col->name + ""); + writeln(QString("%1").arg((col->type ? col->type->toDataType().toFullTypeString() : ""))); + if (col->constraints.size() > 0) + { + writeln(""); + incrIndent(); + for (SqliteCreateTable::Column::Constraint* constr : col->constraints) + { + writeln(""); + incrIndent(); + writeln("" + constr->typeString() + ""); + writeln("" + constr->detokenize() + ""); + decrIndent(); + writeln(""); + } + decrIndent(); + writeln(""); + } + decrIndent(); + writeln(""); + } + decrIndent(); + writeln(""); + + if (createTable->constraints.size() > 0) + { + writeln(""); + incrIndent(); + for (SqliteCreateTable::Constraint* constr : createTable->constraints) + { + writeln(""); + incrIndent(); + writeln("" + constr->typeString() + ""); + writeln("" + constr->detokenize() + ""); + decrIndent(); + writeln(""); + } + decrIndent(); + writeln(""); + } + + writeln(""); + incrIndent(); + return true; +} + +bool XmlExport::exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, const QHash providedData) +{ + UNUSED(providedData); + + if (isTableExport()) + { + setupConfig(); + write(docBegin.arg(codecName)); + } + + writeln(QString("").arg(isTableExport() ? nsStr : "")); + incrIndent(); + + writeln("" + escape(database) + ""); + writeln("" + escape(table) + ""); + writeln("true"); + writeln("" + escape(createTable->module) + ""); + + writeln("" + escape(ddl) + ""); + + writeln(""); + incrIndent(); + for (const QString& col : columnNames) + { + writeln(""); + incrIndent(); + writeln(""+ col + ""); + decrIndent(); + writeln(""); + } + decrIndent(); + writeln(""); + + if (createTable->args.size() > 0) + { + writeln(""); + incrIndent(); + for (const QString& arg : createTable->args) + writeln("" + arg + ""); + + decrIndent(); + writeln(""); + } + + writeln(""); + incrIndent(); + return true; +} + +bool XmlExport::exportTableRow(SqlResultsRowPtr data) +{ + return exportQueryResultsRow(data); +} + +bool XmlExport::afterExportTable() +{ + decrIndent(); + writeln(""); + decrIndent(); + writeln(""); + return true; +} + +bool XmlExport::beforeExportDatabase(const QString& database) +{ + setupConfig(); + write(docBegin.arg(codecName)); + + writeln(QString("").arg(nsStr)); + incrIndent(); + writeln("" + escape(database) + ""); + + return true; +} + +bool XmlExport::exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) +{ + writeln(""); + incrIndent(); + + writeln("" + escape(database) + ""); + writeln("" + escape(name) + ""); + if (createIndex->uniqueKw) + writeln("true"); + + if (createIndex->where) + writeln("" + createIndex->where->detokenize() + ""); + + writeln("" + escape(ddl) + ""); + + decrIndent(); + writeln(""); + return true; +} + +bool XmlExport::exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) +{ + UNUSED(createTrigger); + writeln(""); + incrIndent(); + + writeln("" + escape(database) + ""); + writeln("" + escape(name) + ""); + writeln("" + escape(ddl) + ""); + + QString timing = SqliteCreateTrigger::time(createTrigger->eventTime); + writeln("" + escape(timing) + ""); + + QString event = createTrigger->event ? SqliteCreateTrigger::Event::typeToString(createTrigger->event->type) : ""; + writeln("" + escape(event) + ""); + + QString tag; + if (createTrigger->eventTime == SqliteCreateTrigger::Time::INSTEAD_OF) + tag = ""; + else + tag = ""; + + writeln(tag + escape(createTrigger->table) + tag); + + if (createTrigger->precondition) + writeln("" + escape(createTrigger->precondition->detokenize()) + ""); + + QStringList queryStrings; + for (SqliteQuery* q : createTrigger->queries) + queryStrings << q->detokenize(); + + writeln("" + escape(queryStrings.join("\n")) + ""); + + decrIndent(); + writeln(""); + return true; +} + +bool XmlExport::exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr createView) +{ + UNUSED(createView); + writeln(""); + incrIndent(); + + writeln("" + escape(database) + ""); + writeln("" + escape(name) + ""); + writeln("" + escape(ddl) + ""); + writeln(""); + decrIndent(); + writeln(""); + return true; +} + +bool XmlExport::afterExportDatabase() +{ + decrIndent(); + writeln(""); + return true; +} + +void XmlExport::setupConfig() +{ + codecName = codec->name(); + indentDepth = 0; + newLineStr = ""; + indentStr = ""; + indent = (cfg.XmlExport.Format.get() == "format"); + if (indent) + newLineStr = "\n"; + + nsStr = QString(); + if (cfg.XmlExport.UseNamespace.get()) + nsStr = " xmlns=\"" + cfg.XmlExport.Namespace.get() + "\""; + + if (cfg.XmlExport.Escaping.get() == "ampersand") + { + useAmpersand = true; + useCdata = false; + } + else if (cfg.XmlExport.Escaping.get() == "cdata") + { + useAmpersand = false; + useCdata = true; + } + else + { + useAmpersand = true; + useCdata = true; + } +} + +void XmlExport::incrIndent() +{ + if (indent) + { + indentDepth++; + updateIndent(); + } +} + +void XmlExport::decrIndent() +{ + if (indent) + { + indentDepth--; + updateIndent(); + } +} + +void XmlExport::updateIndent() +{ + indentStr = QString(" ").repeated(indentDepth); +} + +void XmlExport::writeln(const QString& str) +{ + QString newStr; + if (str.contains("\n")) + { + QStringList lines = str.split("\n"); + QMutableStringListIterator it(lines); + while (it.hasNext()) + it.value().prepend(indentStr); + + newStr = lines.join("\n") + newLineStr; + } + else + { + newStr = indentStr + str + newLineStr; + } + GenericExportPlugin::write(newStr); +} + +QString XmlExport::escape(const QString& str) +{ + if (useAmpersand && useCdata) + { + if (str.length() >= minLenghtForCdata) + return escapeCdata(str); + else + return escapeAmpersand(str); + } + else if (useAmpersand) + { + return escapeAmpersand(str); + } + else + { + return escapeCdata(str); + } +} + +QString XmlExport::escapeCdata(const QString& str) +{ + if (str.contains('"') || str.contains('&') || str.contains('<') || str.contains('>')) + return ""; + + return str; +} + +QString XmlExport::escapeAmpersand(const QString& str) +{ + return str.toHtmlEscaped(); +} + +QString XmlExport::toString(bool value) +{ + return value ? "true" : "false"; +} + +bool XmlExport::init() +{ + Q_INIT_RESOURCE(xmlexport); + return GenericExportPlugin::init(); +} + +void XmlExport::deinit() +{ + Q_CLEANUP_RESOURCE(xmlexport); +} diff --git a/Plugins/XmlExport/xmlexport.h b/Plugins/XmlExport/xmlexport.h new file mode 100644 index 0000000..271a687 --- /dev/null +++ b/Plugins/XmlExport/xmlexport.h @@ -0,0 +1,75 @@ +#ifndef XMLEXPORT_H +#define XMLEXPORT_H + +#include "xmlexport_global.h" +#include "plugins/genericexportplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(XmlExportConfig, + CFG_CATEGORY(XmlExport, + CFG_ENTRY(QString, Format, "format") + CFG_ENTRY(bool, UseNamespace, false) + CFG_ENTRY(QString, Namespace, QString()) + CFG_ENTRY(QString, Escaping, "mixed") + ) +) + +class XMLEXPORTSHARED_EXPORT XmlExport : public GenericExportPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("xmlexport.json") + + public: + XmlExport(); + + QString getFormatName() const; + ExportManager::StandardConfigFlags standardOptionsToEnable() const; + QString getExportConfigFormName() const; + CfgMain* getConfig(); + void validateOptions(); + QString defaultFileExtension() const; + bool beforeExportQueryResults(const QString& query, QList& columns, + const QHash providedData); + bool exportQueryResultsRow(SqlResultsRowPtr row); + bool afterExportQueryResults(); + bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash providedData); + bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, + const QHash providedData); + bool exportTableRow(SqlResultsRowPtr data); + bool afterExportTable(); + bool beforeExportDatabase(const QString& database); + bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex); + bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger); + bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr createView); + bool afterExportDatabase(); + bool init(); + void deinit(); + + private: + void setupConfig(); + void incrIndent(); + void decrIndent(); + void updateIndent(); + void writeln(const QString& str); + QString escape(const QString& str); + QString escapeCdata(const QString& str); + QString escapeAmpersand(const QString& str); + + static QString toString(bool value); + + CFG_LOCAL(XmlExportConfig, cfg) + bool indent = false; + int indentDepth = 0; + QString indentStr; + QString newLineStr; + QString nsStr; + QString codecName; + bool useAmpersand = true; + bool useCdata = true; + static const QString docBegin; + + static constexpr int minLenghtForCdata = 100; +}; + +#endif // XMLEXPORT_H diff --git a/Plugins/XmlExport/xmlexport.json b/Plugins/XmlExport/xmlexport.json new file mode 100644 index 0000000..2432cdd --- /dev/null +++ b/Plugins/XmlExport/xmlexport.json @@ -0,0 +1,7 @@ +{ + "type": "ExportPlugin", + "title": "XML export", + "description": "Provides XML format for exporting.", + "version": 10000, + "author": "SalSoft" +} diff --git a/Plugins/XmlExport/xmlexport.qrc b/Plugins/XmlExport/xmlexport.qrc new file mode 100644 index 0000000..d6a77a6 --- /dev/null +++ b/Plugins/XmlExport/xmlexport.qrc @@ -0,0 +1,5 @@ + + + XmlExport.ui + + diff --git a/Plugins/XmlExport/xmlexport_global.h b/Plugins/XmlExport/xmlexport_global.h new file mode 100644 index 0000000..fb1f244 --- /dev/null +++ b/Plugins/XmlExport/xmlexport_global.h @@ -0,0 +1,12 @@ +#ifndef XMLEXPORT_GLOBAL_H +#define XMLEXPORT_GLOBAL_H + +#include + +#if defined(XMLEXPORT_LIBRARY) +# define XMLEXPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define XMLEXPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // XMLEXPORT_GLOBAL_H diff --git a/SQLiteStudio3/SQLiteStudio3.pro b/SQLiteStudio3/SQLiteStudio3.pro new file mode 100644 index 0000000..66efea7 --- /dev/null +++ b/SQLiteStudio3/SQLiteStudio3.pro @@ -0,0 +1,46 @@ +TEMPLATE = subdirs + +core.subdir = coreSQLiteStudio + +tests.subdir = Tests +tests.depends = core + +gui.subdir = guiSQLiteStudio +gui.depends = core + +cli.subdir = sqlitestudiocli +cli.depends = core + +gui_app.subdir = sqlitestudio +gui_app.depends = gui + +update_app.subdir = UpdateSQLiteStudio +update_app.depends = core + +SUBDIRS += \ + core \ + gui \ + cli \ + gui_app + +if(contains(DEFINES,tests)) { + SUBDIRS += tests +} + +win32: { + SUBDIRS += update_app +} + +linux: { + portable.commands = sh $$PWD/create_linux_portable.sh $$PWD/../output $$QMAKE_QMAKE + tgz.commands = sh $$PWD/create_linux_portable.sh $$PWD/../output $$QMAKE_QMAKE tgz + dist.commands = sh $$PWD/create_linux_portable.sh $$PWD/../output $$QMAKE_QMAKE dist + QMAKE_EXTRA_TARGETS += portable tgz dist +} + +macx: { + bundle.commands = sh $$PWD/create_macosx_bundle.sh $$PWD/../output $$QMAKE_QMAKE + dmg.commands = sh $$PWD/create_macosx_bundle.sh $$PWD/../output $$QMAKE_QMAKE dmg + dist.commands = sh $$PWD/create_macosx_bundle.sh $$PWD/../output $$QMAKE_QMAKE dist + QMAKE_EXTRA_TARGETS += bundle dmg dist +} diff --git a/SQLiteStudio3/Tests/CompletionHelperTest/CompletionHelperTest.pro b/SQLiteStudio3/Tests/CompletionHelperTest/CompletionHelperTest.pro new file mode 100644 index 0000000..d4c1e99 --- /dev/null +++ b/SQLiteStudio3/Tests/CompletionHelperTest/CompletionHelperTest.pro @@ -0,0 +1,21 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-03-29T21:35:35 +# +#------------------------------------------------- + +include($$PWD/../TestUtils/test_common.pri) + +QT += testlib +QT -= gui + +TARGET = tst_completionhelpertest +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += tst_completionhelpertest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" + +HEADERS += diff --git a/SQLiteStudio3/Tests/CompletionHelperTest/tst_completionhelpertest.cpp b/SQLiteStudio3/Tests/CompletionHelperTest/tst_completionhelpertest.cpp new file mode 100644 index 0000000..fc21a55 --- /dev/null +++ b/SQLiteStudio3/Tests/CompletionHelperTest/tst_completionhelpertest.cpp @@ -0,0 +1,398 @@ +#include "completionhelper.h" +#include "expectedtoken.h" +#include "dbsqlite3mock.h" +#include "parser/lexer.h" +#include "parser/token.h" +#include "parser/keywords.h" +#include "sqlitestudio.h" +#include "mocks.h" +#include +#include +#include + + +class CompletionHelperTest : public QObject +{ + Q_OBJECT + + public: + CompletionHelperTest(); + + private: + QList getEntryList(QList tokens); + QSet getTypeList(QList tokens); + + Db* db = nullptr; + + bool contains(const QList& tokens, ExpectedToken::Type type); + bool contains(const QList& tokens, ExpectedToken::Type type, + const QString& value); + bool contains(const QList& tokens, ExpectedToken::Type type, + const QString& value, const QString& prefix); + bool contains(const QList &tokens, ExpectedToken::Type type, + const QString &value, const QString &prefix, const QString &contextInfo); + + int find(const QList& tokens, ExpectedToken::Type type); + int find(const QList& tokens, ExpectedToken::Type type, + const QString& value); + int find(const QList& tokens, ExpectedToken::Type type, + const QString& value, const QString& prefix); + int find(const QList &tokens, ExpectedToken::Type type, + const QString &value, const QString &prefix, const QString &contextInfo); + + private Q_SLOTS: + void testResCol1(); + void testFrom1(); + void testFrom2(); + void testResCol2(); + void testResCol3(); + void testResCol4(); + void testResCol5(); + void testResCol6(); + void testFromKw(); + void testUpdateTable(); + void testUpdateCols1(); + void initTestCase(); + void cleanupTestCase(); +}; + +CompletionHelperTest::CompletionHelperTest() +{ +} + +QList CompletionHelperTest::getEntryList(QList tokens) +{ + QList entries; + foreach (ExpectedTokenPtr expectedToken, tokens) + entries += expectedToken.data(); + + return entries; +} + +QSet CompletionHelperTest::getTypeList(QList tokens) +{ + QSet entries; + foreach (ExpectedTokenPtr expectedToken, tokens) + entries += expectedToken->type; + + return entries; +} + +bool CompletionHelperTest::contains(const QList &tokens, ExpectedToken::Type type) +{ + return find(tokens, type) > -1; +} + +bool CompletionHelperTest::contains(const QList &tokens, ExpectedToken::Type type, const QString &value) +{ + return find(tokens, type, value) > -1; +} + +bool CompletionHelperTest::contains(const QList &tokens, ExpectedToken::Type type, const QString &value, const QString &prefix) +{ + return find(tokens, type, value, prefix) > -1; +} + +bool CompletionHelperTest::contains(const QList &tokens, ExpectedToken::Type type, const QString &value, const QString &prefix, const QString &contextInfo) +{ + return find(tokens, type, value, prefix, contextInfo) > -1; +} + +int CompletionHelperTest::find(const QList &tokens, ExpectedToken::Type type) +{ + int i = 0; + foreach (ExpectedTokenPtr token, tokens) + { + if (token->type == type) + return i; + + i++; + } + return -1; +} + +int CompletionHelperTest::find(const QList &tokens, ExpectedToken::Type type, const QString &value) +{ + int i = -1; + foreach (ExpectedTokenPtr token, tokens) + { + i++; + if (token->type != type) + continue; + + if (token->value == value) + return i; + } + return -1; +} + +int CompletionHelperTest::find(const QList &tokens, ExpectedToken::Type type, const QString &value, const QString &prefix) +{ + int i = -1; + foreach (ExpectedTokenPtr token, tokens) + { + i++; + if (token->type != type) + continue; + + if (token->value != value) + continue; + + if (token->prefix == prefix) + return i; + } + return -1; +} + +int CompletionHelperTest::find(const QList &tokens, ExpectedToken::Type type, const QString &value, const QString &prefix, const QString &contextInfo) +{ + int i = -1; + foreach (ExpectedTokenPtr token, tokens) + { + i++; + if (token->type != type) + continue; + + if (token->value != value) + continue; + + if (token->prefix != prefix) + continue; + + if (token->contextInfo == contextInfo) + return i; + } + return -1; +} + +void CompletionHelperTest::testFrom1() +{ + QString sql = "select * FROM "; + CompletionHelper helper(sql, db); + QList tokens = helper.getExpectedTokens().filtered(); + + QVERIFY(contains(tokens, ExpectedToken::TABLE, "test")); + QVERIFY(contains(tokens, ExpectedToken::TABLE, "sqlite_master")); + QVERIFY(contains(tokens, ExpectedToken::TABLE, "sqlite_temp_master")); + QVERIFY(contains(tokens, ExpectedToken::DATABASE, "main")); +} + +void CompletionHelperTest::testFrom2() +{ + QString sql = "select id from abc, "; + CompletionHelper helper(sql, db); + QList tokens = helper.getExpectedTokens().filtered(); + +// QList entries = getEntryList(tokens); + + QVERIFY(contains(tokens, ExpectedToken::TABLE)); + QVERIFY(contains(tokens, ExpectedToken::DATABASE)); + QVERIFY(!contains(tokens, ExpectedToken::FUNCTION)); + QVERIFY(!contains(tokens, ExpectedToken::COLUMN)); + + // Because abc was already used, the order should be: + // test + // sqlite_master + // sqlite_temp_master + // abc + QVERIFY(find(tokens, ExpectedToken::TABLE, "test") == 0); + QVERIFY(find(tokens, ExpectedToken::TABLE, "sqlite_master") == 1); + QVERIFY(find(tokens, ExpectedToken::TABLE, "sqlite_temp_master") == 2); + QVERIFY(find(tokens, ExpectedToken::TABLE, "abc") == 3); +} + +void CompletionHelperTest::testResCol1() +{ + QString sql = "select main.test."; + CompletionHelper helper(sql, db); + QList tokens = helper.getExpectedTokens().filtered(); + + QVERIFY(!contains(tokens, ExpectedToken::COLUMN, "name")); + QVERIFY(contains(tokens, ExpectedToken::COLUMN, "id")); + QVERIFY(contains(tokens, ExpectedToken::COLUMN, "val")); +} + +void CompletionHelperTest::testResCol2() +{ + QString sql = "select "; + CompletionHelper helper(sql, db); + QList tokens = helper.getExpectedTokens().filtered(); + + //QList entries = getEntryList(tokens); + + // Exclude JOIN keywords and FK MATCH keywords + QVERIFY(!contains(tokens, ExpectedToken::KEYWORD, "NATURAL")); + QVERIFY(!contains(tokens, ExpectedToken::KEYWORD, "CROSS")); + QVERIFY(!contains(tokens, ExpectedToken::KEYWORD, "SIMPLE")); + QVERIFY(!contains(tokens, ExpectedToken::KEYWORD, "FULL")); + + // Exclude OTHER as there is at least one CTX token available + QVERIFY(!contains(tokens, ExpectedToken::OTHER)); + + QVERIFY(contains(tokens, ExpectedToken::DATABASE)); + QVERIFY(contains(tokens, ExpectedToken::FUNCTION)); + QVERIFY(contains(tokens, ExpectedToken::TABLE, "sqlite_master")); + QVERIFY(contains(tokens, ExpectedToken::COLUMN, "name", QString::null)); + QVERIFY(contains(tokens, ExpectedToken::COLUMN, "id", QString::null)); +} + +void CompletionHelperTest::testResCol3() +{ + QString sql = "select from test"; + CompletionHelper helper(sql, 7, db); + QList tokens = helper.getExpectedTokens().filtered(); + + //QList entries = getEntryList(tokens); + + QVERIFY(contains(tokens, ExpectedToken::TABLE)); + QVERIFY(contains(tokens, ExpectedToken::DATABASE)); + QVERIFY(contains(tokens, ExpectedToken::FUNCTION)); + QVERIFY(contains(tokens, ExpectedToken::COLUMN, "id", QString::null)); + QVERIFY(contains(tokens, ExpectedToken::COLUMN, "val", QString::null)); + + // Order should be: + // id - test (no prefix value) + // val - test (default, no prefix value) + // val2 - test (default, no prefix value) + // id - abc (no prefix, we didn't mention other table in from clause) + QVERIFY(find(tokens, ExpectedToken::COLUMN, "id", QString::null, "test") == 0); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "val", QString::null, "test") == 1); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "val2", QString::null, "test") == 2); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "id", QString::null, "abc") == 3); +} + +void CompletionHelperTest::testResCol4() +{ + QString sql = "select test.id, from test"; + CompletionHelper helper(sql, 15, db); + QList tokens = helper.getExpectedTokens().filtered(); + + //QList entries = getEntryList(tokens); + + QVERIFY(contains(tokens, ExpectedToken::TABLE)); + QVERIFY(contains(tokens, ExpectedToken::DATABASE)); + QVERIFY(contains(tokens, ExpectedToken::FUNCTION)); + + // Because test.id was already used, the order should be: + // val - test (default, no prefix value) + // val2 - test (default, no prefix value) + // id - test (no prefix, only one table in FROM) + // id - abc (no prefix, only one table in FROM) + QVERIFY(find(tokens, ExpectedToken::COLUMN, "val", QString::null, "test") == 0); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "val2", QString::null, "test") == 1); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "id", QString::null, "test") == 2); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "id", QString::null, "abc") == 3); +} + +void CompletionHelperTest::testResCol5() +{ + QString sql = "select test.id, val from test"; + CompletionHelper helper(sql, 15, db); + QList tokens = helper.getExpectedTokens().filtered(); + + //QList entries = getEntryList(tokens); + + QVERIFY(contains(tokens, ExpectedToken::TABLE)); + QVERIFY(contains(tokens, ExpectedToken::DATABASE)); + QVERIFY(contains(tokens, ExpectedToken::FUNCTION)); + + // Because test.id and val were already used, the order should be: + // val2 - test (default, no prefix value) + // id - test + // val - test (default, no prefix value) + // id - abc (no prefix, only one table in FROM) + QVERIFY(find(tokens, ExpectedToken::COLUMN, "val2") == 0); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "id", QString::null, "test") == 1); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "val") == 2); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "id", QString::null, "abc") == 3); +} + +void CompletionHelperTest::testResCol6() +{ + QString sql = "select (select from sqlite_master as S, sqlite_master as S2) from test as A"; + CompletionHelper helper(sql, 15, db); + QList tokens = helper.getExpectedTokens().filtered(); + + //QList entries = getEntryList(tokens); + + QVERIFY(contains(tokens, ExpectedToken::TABLE)); + QVERIFY(contains(tokens, ExpectedToken::DATABASE)); + QVERIFY(contains(tokens, ExpectedToken::FUNCTION)); + + // Desired order: + // S.name - sqlite_master S (can be second or first) + // S.name - sqlite_master S2 (can be second or first) + // ... + // id - test (default, no prefix) + // ... + // id - abc (no prefix, only one table with this column in FROM) + // ... + int s_sqlite_master_name = find(tokens, ExpectedToken::COLUMN, "name", "S", "sqlite_master"); + int s2_sqlite_master_name = find(tokens, ExpectedToken::COLUMN, "name", "S2", "sqlite_master"); + int test_id = find(tokens, ExpectedToken::COLUMN, "id", "A", "test"); + int abc_id = find(tokens, ExpectedToken::COLUMN, "id", QString::null, "abc"); + QVERIFY(s_sqlite_master_name <= 1); + QVERIFY(s2_sqlite_master_name <= 1); + QVERIFY(test_id > s2_sqlite_master_name); + QVERIFY(abc_id > test_id); +} + +void CompletionHelperTest::testFromKw() +{ + QString sql = "select * FR"; + CompletionHelper helper(sql, db); + QList tokens = helper.getExpectedTokens().filtered(); + + QVERIFY(contains(tokens, ExpectedToken::KEYWORD, "FROM")); + QVERIFY(!contains(tokens, ExpectedToken::COLUMN)); + QVERIFY(tokens.size() == 1); +} + +void CompletionHelperTest::testUpdateTable() +{ + QString sql = "update "; + CompletionHelper helper(sql, db); + QList tokens = helper.getExpectedTokens().filtered(); + + QVERIFY(contains(tokens, ExpectedToken::TABLE)); +} + +void CompletionHelperTest::testUpdateCols1() +{ + QString sql = "update test set id = 5, "; + CompletionHelper helper(sql, db); + QList tokens = helper.getExpectedTokens().filtered(); + + // TODO if table is provided and there is at least one column in proposal for it, then skip columns from other tables. + // TODO Make the context more precise - distinguish between left side column from right side expression + + QVERIFY(find(tokens, ExpectedToken::COLUMN, "id", QString::null, "test") == 0); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "val", QString::null, "test") == 1); + QVERIFY(find(tokens, ExpectedToken::COLUMN, "val2", QString::null, "test") == 2); + //QVERIFY(!contains(tokens, ExpectedToken::COLUMN, "id", QString::null, "abc")); // TODO +} + +void CompletionHelperTest::initTestCase() +{ + initKeywords(); + Lexer::staticInit(); + CompletionHelper::init(); + initMocks(); + + db = new DbSqlite3Mock("testdb"); + db->open(); + db->exec("CREATE TABLE test (id int, val text, val2 text);"); + db->exec("CREATE TABLE abc (id int, xyz text);"); +} + +void CompletionHelperTest::cleanupTestCase() +{ + db->close(); + delete db; + db = nullptr; + deleteMockRepo(); +} + +QTEST_APPLESS_MAIN(CompletionHelperTest) + +#include "tst_completionhelpertest.moc" diff --git a/SQLiteStudio3/Tests/DbVersionConverterTest/DbVersionConverterTest.pro b/SQLiteStudio3/Tests/DbVersionConverterTest/DbVersionConverterTest.pro new file mode 100644 index 0000000..cc50c4d --- /dev/null +++ b/SQLiteStudio3/Tests/DbVersionConverterTest/DbVersionConverterTest.pro @@ -0,0 +1,21 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-05-04T00:44:01 +# +#------------------------------------------------- + +include($$PWD/../TestUtils/test_common.pri) + +QT += testlib + +QT -= gui + +TARGET = tst_dbversionconvertertesttest +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + + +SOURCES += tst_dbversionconvertertesttest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/SQLiteStudio3/Tests/DbVersionConverterTest/tst_dbversionconvertertesttest.cpp b/SQLiteStudio3/Tests/DbVersionConverterTest/tst_dbversionconvertertesttest.cpp new file mode 100644 index 0000000..7d69e7e --- /dev/null +++ b/SQLiteStudio3/Tests/DbVersionConverterTest/tst_dbversionconvertertesttest.cpp @@ -0,0 +1,119 @@ +#include "dbversionconverter.h" +#include "parser/lexer.h" +#include "parser/keywords.h" +#include +#include + +class DbVersionConverterTestTest : public QObject +{ + Q_OBJECT + + public: + DbVersionConverterTestTest(); + + private Q_SLOTS: + void initTestCase(); + void init(); + void cleanupTestCase(); + void testColumnAutoIncr(); + void testIndexedColumn(); + void testInsertMultiValues(); + void testSelectWith(); + void testTableWithoutRowId(); + void testTableWithDefaultCtime(); + + private: + void printErrors(); + + DbVersionConverter* converter = nullptr; +}; + +DbVersionConverterTestTest::DbVersionConverterTestTest() +{ +} + +void DbVersionConverterTestTest::testColumnAutoIncr() +{ + QString query = "CREATE TABLE test (col INTEGER PRIMARY KEY AUTOINCREMENT);"; + QString result = converter->convert3To2(query); + + printErrors(); + QVERIFY(converter->getErrors().size() == 0); + QVERIFY(result == "CREATE TABLE test (col INTEGER PRIMARY KEY);"); +} + +void DbVersionConverterTestTest::testIndexedColumn() +{ + QString query = "CREATE INDEX idx ON test (col COLLATE NOCASE ASC);"; + QString result = converter->convert3To2(query); + + printErrors(); + QVERIFY(converter->getErrors().size() == 0); + QVERIFY(result == "CREATE INDEX idx ON test (col ASC);"); +} + +void DbVersionConverterTestTest::testInsertMultiValues() +{ + QString query = "INSERT INTO test (col1, col2) VALUES (1, 'a'), (2, 'b');"; + QString result = converter->convert3To2(query); + + printErrors(); + QVERIFY(converter->getErrors().size() == 0); + QVERIFY(result == "INSERT INTO test (col1, col2) SELECT 1, 'a' UNION ALL SELECT 2, 'b';"); +} + +void DbVersionConverterTestTest::testSelectWith() +{ + QString query = "WITH RECURSIVE cnt (x) AS (VALUES(1) UNION ALL SELECT x + 1 FROM cnt WHERE x < 1000000) SELECT x FROM cnt;"; + QString result = converter->convert3To2(query); + + QVERIFY(converter->getErrors().size() == 1); + QVERIFY(result == ";"); +} + +void DbVersionConverterTestTest::testTableWithoutRowId() +{ + QString query = "CREATE TABLE test (col PRIMARY KEY) WITHOUT ROWID;"; + QString result = converter->convert3To2(query); + + printErrors(); + QVERIFY(converter->getErrors().size() == 0); + QVERIFY(result == "CREATE TABLE test (col PRIMARY KEY);"); +} + +void DbVersionConverterTestTest::testTableWithDefaultCtime() +{ + QString query = "CREATE TABLE test (col INT DEFAULT current_date NOT NULL);"; + QString result = converter->convert3To2(query); + + printErrors(); + QVERIFY(converter->getErrors().size() == 0); + QVERIFY(result == "CREATE TABLE test (col INT NOT NULL);"); +} + +void DbVersionConverterTestTest::printErrors() +{ + for (const QString& err : converter->getErrors()) + qWarning() << err; +} + +void DbVersionConverterTestTest::initTestCase() +{ + initKeywords(); + Lexer::staticInit(); + converter = new DbVersionConverter(); +} + +void DbVersionConverterTestTest::init() +{ + converter->reset(); +} + +void DbVersionConverterTestTest::cleanupTestCase() +{ + delete converter; +} + +QTEST_APPLESS_MAIN(DbVersionConverterTestTest) + +#include "tst_dbversionconvertertesttest.moc" diff --git a/SQLiteStudio3/Tests/DsvFormatsTest/DsvFormatsTest.pro b/SQLiteStudio3/Tests/DsvFormatsTest/DsvFormatsTest.pro new file mode 100644 index 0000000..42a08d3 --- /dev/null +++ b/SQLiteStudio3/Tests/DsvFormatsTest/DsvFormatsTest.pro @@ -0,0 +1,20 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-11-21T20:58:39 +# +#------------------------------------------------- + +include($$PWD/../TestUtils/test_common.pri) + +QT += testlib + +QT -= gui + +TARGET = tst_dsvformatstesttest +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += tst_dsvformatstesttest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/SQLiteStudio3/Tests/DsvFormatsTest/tst_dsvformatstesttest.cpp b/SQLiteStudio3/Tests/DsvFormatsTest/tst_dsvformatstesttest.cpp new file mode 100644 index 0000000..48e7280 --- /dev/null +++ b/SQLiteStudio3/Tests/DsvFormatsTest/tst_dsvformatstesttest.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include "tsvserializer.h" +#include "csvserializer.h" + +// TODO Add tests for CsvSerializer + +class DsvFormatsTestTest : public QObject +{ + Q_OBJECT + + public: + DsvFormatsTestTest(); + + private: + QList sampleData; + QString sampleTsv; + + private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testTsv1(); +}; + +DsvFormatsTestTest::DsvFormatsTestTest() +{ +} + +void DsvFormatsTestTest::initTestCase() +{ + sampleData << QStringList{"a", "b c", "\"d\""}; + sampleData << QStringList{"a\"a\"", "\"b\"c\"", "d\"\"e"}; + sampleData << QStringList{"a\na", "b\tc", "d\t\"e"}; + +#ifdef Q_OS_MACX + QString lineSep = "\r"; +#else + QString lineSep = "\n"; +#endif + + sampleTsv = ""; + sampleTsv += "a\tb c\t\"d\""; + sampleTsv += lineSep; + sampleTsv += "a\"a\"\t\"b\"c\"\td\"\"e"; + sampleTsv += lineSep; + sampleTsv += "\"a\na\"\t\"b\tc\"\t\"d\t\"\"e\""; +} + +void DsvFormatsTestTest::cleanupTestCase() +{ +} + +void DsvFormatsTestTest::testTsv1() +{ + QString result = TsvSerializer::serialize(sampleData); + + QString common = ""; + int i; + if (result != sampleTsv) + { + int lgt = qMax(result.length(), sampleTsv.length()); + for (i = 0; i < lgt && result[i] == sampleTsv[i]; i++) + common.append(result[i]); + } + + QVERIFY2(result == sampleTsv, QString("Mismatch after %1: %2\nSample: %3\nGot : %4").arg(i).arg(common, sampleTsv, result).toLocal8Bit().data()); +} + +QTEST_APPLESS_MAIN(DsvFormatsTestTest) + +#include "tst_dsvformatstesttest.moc" diff --git a/SQLiteStudio3/Tests/HashTablesTest/HashTablesTest.pro b/SQLiteStudio3/Tests/HashTablesTest/HashTablesTest.pro new file mode 100644 index 0000000..c0e071e --- /dev/null +++ b/SQLiteStudio3/Tests/HashTablesTest/HashTablesTest.pro @@ -0,0 +1,21 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-02-12T16:34:55 +# +#------------------------------------------------- + +include($$PWD/../TestUtils/test_common.pri) + +QT += testlib + +QT -= gui + +TARGET = tst_hashtablestesttest +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + + +SOURCES += tst_hashtablestesttest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/SQLiteStudio3/Tests/HashTablesTest/tst_hashtablestesttest.cpp b/SQLiteStudio3/Tests/HashTablesTest/tst_hashtablestesttest.cpp new file mode 100644 index 0000000..f729e37 --- /dev/null +++ b/SQLiteStudio3/Tests/HashTablesTest/tst_hashtablestesttest.cpp @@ -0,0 +1,219 @@ +#include "common/strhash.h" +#include "common/bistrhash.h" +#include "common/bihash.h" +#include +#include +#include + +class HashTablesTestTest : public QObject +{ + Q_OBJECT + + public: + HashTablesTestTest(); + + private Q_SLOTS: + void strHash1(); + void strHash2(); + void strHash3(); + void strHash4(); + void strHash5(); + void strHash6(); + void biStrHash1(); + void biStrHash2(); + void biStrHash3(); + void biHash1(); + void biHash2(); +}; + +HashTablesTestTest::HashTablesTestTest() +{ +} + +void HashTablesTestTest::strHash1() +{ + StrHash hash; + hash.insert("key1", "value1"); + hash.insert("key2", "value2"); + hash.insert("KEY2", "value3"); + + QVERIFY(hash.contains("KEY1", Qt::CaseInsensitive)); + QVERIFY(!hash.contains("KEY1", Qt::CaseSensitive)); + QVERIFY(hash.contains("key2", Qt::CaseInsensitive)); + QVERIFY(!hash.contains("key2", Qt::CaseSensitive)); + QVERIFY(hash.value("key2", Qt::CaseInsensitive) == "value3"); +} + +void HashTablesTestTest::strHash2() +{ + StrHash hash; + hash.insert("key1", "value1"); + hash.insert("KEY2", "value2"); + hash.insert("KEY3", "value3"); + + hash.remove("key2"); + QVERIFY(hash.count() == 3); + + hash.remove("key2", Qt::CaseInsensitive); + QVERIFY(hash.count() == 2); +} + +void HashTablesTestTest::strHash3() +{ + StrHash hash1; + hash1.insert("key1", "value1"); + hash1.insert("KEY2", "value2"); + hash1.insert("KEY3", "value3"); + + StrHash hash2; + hash2.insert("key3", "value6"); + hash2.insert("KEY4", "value7"); + hash2.insert("KEY5", "value8"); + + hash1.unite(hash2); + + QVERIFY(hash1.count() == 5); + QVERIFY(hash1.contains("key3", Qt::CaseSensitive)); + QVERIFY(hash1.value("key3", Qt::CaseInsensitive) == "value6"); +} + +void HashTablesTestTest::strHash4() +{ + StrHash hash; + hash["key1"] = "value1"; + hash["KEY2"] = "value2"; + hash["KEY3"] = "value3"; + hash["key3"] = "value4"; + + QVERIFY(hash.count() == 3); + QVERIFY(hash.value("key3", Qt::CaseSensitive) == "value4"); + QVERIFY(hash.values().size() == 3); + QVERIFY(hash.keys().size() == 3); +} + +void HashTablesTestTest::strHash5() +{ + StrHash hash; + hash["key1"] = "value1"; + hash["KEY2"] = "value2"; + hash["KEY3"] = "value3"; + hash["key3"] = "value4"; + + QVERIFY(hash.count("key1", Qt::CaseInsensitive) == 1); + QVERIFY(hash.count("key2", Qt::CaseInsensitive) == 1); + QVERIFY(hash.count("key3", Qt::CaseInsensitive) == 1); + QVERIFY(hash.count("KEY3", Qt::CaseInsensitive) == 1); + QVERIFY(hash.count("key1", Qt::CaseSensitive) == 1); + QVERIFY(hash.count("key2", Qt::CaseSensitive) == 0); + QVERIFY(hash.count("key3", Qt::CaseSensitive) == 1); +} + +void HashTablesTestTest::strHash6() +{ + StrHash hash; + hash["key1"] = "value1"; + hash["KEY2"] = "value2"; + hash["KEY3"] = "value3"; + + QVERIFY(hash["key3"] == "value3"); +} + +void HashTablesTestTest::biStrHash1() +{ + BiStrHash hash; + hash.insert("left1", "right1"); + hash.insert("LEFT2", "right2"); + hash.insert("left3", "RIGHT3"); + hash.insert("LEFT4", "RIGHT4"); + + QVERIFY(hash.count() == 4); + QVERIFY(hash.containsLeft("left1", Qt::CaseSensitive)); + QVERIFY(!hash.containsLeft("left2", Qt::CaseSensitive)); + QVERIFY(hash.containsLeft("left2", Qt::CaseInsensitive)); + QVERIFY(hash.containsRight("right1", Qt::CaseSensitive)); + QVERIFY(!hash.containsRight("right3", Qt::CaseSensitive)); + QVERIFY(hash.containsRight("right3", Qt::CaseInsensitive)); + QVERIFY(hash.valueByLeft("left4", Qt::CaseInsensitive) == "RIGHT4"); +} + +void HashTablesTestTest::biStrHash2() +{ + BiStrHash hash; + hash.insert("left1", "right1"); + hash.insert("LEFT2", "right2"); + hash.insert("left3", "RIGHT3"); + hash.insert("LEFT4", "RIGHT4"); + hash.insert("LEFT5", "RIGHT5"); + hash.insert("LEFT6", "RIGHT6"); + hash.insert("LEFT7", "RIGHT7"); + hash.insert("left5", "x"); + hash.insert("y", "right6"); + + QVERIFY(hash.count() == 7); + + hash.removeLeft("left2"); + hash.removeLeft("left3"); + hash.removeLeft("left4", Qt::CaseInsensitive); + hash.removeRight("RIGHT1"); + hash.removeRight("right7", Qt::CaseInsensitive); + + QVERIFY(hash.count() == 4); + QVERIFY(!hash.containsLeft("LEFT5")); + QVERIFY(hash.valueByLeft("LEFT5", Qt::CaseInsensitive) == "x"); + QVERIFY(!hash.containsRight("RIGHT6")); + QVERIFY(hash.valueByRight("RIGHT6", Qt::CaseInsensitive) == "y"); +} + +void HashTablesTestTest::biStrHash3() +{ + BiStrHash hash; + hash.insert("left1", "right1"); + hash.insert("LEFT2", "RIGHT2"); + + QVERIFY(hash.count() == 2); + + hash.insert("left2", "RIGHT1"); + + QVERIFY(hash.count() == 1); + QVERIFY(hash.valueByLeft("left2") == "RIGHT1"); + QVERIFY(hash.valueByRight("RIGHT1") == "left2"); + QVERIFY(hash.valueByLeft("LEFT2", Qt::CaseInsensitive) == "RIGHT1"); + QVERIFY(hash.valueByRight("right1", Qt::CaseInsensitive) == "left2"); +} + +void HashTablesTestTest::biHash1() +{ + BiHash hash; + hash.insert("left", "right"); + hash.insert("LEFT", "RIGHT"); + + QVERIFY(hash.count() == 2); + QVERIFY(hash.valueByLeft("left") == "right"); + QVERIFY(hash.valueByRight("RIGHT") == "LEFT"); + + QVERIFY(hash.removeLeft("x") == 0); + QVERIFY(hash.removeLeft("left") == 1); + QVERIFY(hash.count() == 1); + QVERIFY(hash.removeRight("RIGHT") == 1); + QVERIFY(hash.count() == 0); +} + +void HashTablesTestTest::biHash2() +{ + BiHash hash; + hash.insert("left", "right"); + hash.insert("LEFT", "RIGHT"); + + QVERIFY(hash.count() == 2); + + hash.insert("left", "RIGHT"); + + QVERIFY(hash.count() == 1); + QVERIFY(hash.valueByLeft("left") == "RIGHT"); + QVERIFY(hash.takeRight("RIGHT") == "left"); + QVERIFY(hash.count() == 0); +} + +QTEST_APPLESS_MAIN(HashTablesTestTest) + +#include "tst_hashtablestesttest.moc" diff --git a/SQLiteStudio3/Tests/ParserTest/ParserTest.pro b/SQLiteStudio3/Tests/ParserTest/ParserTest.pro new file mode 100644 index 0000000..baccb77 --- /dev/null +++ b/SQLiteStudio3/Tests/ParserTest/ParserTest.pro @@ -0,0 +1,20 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-05-30T16:51:11 +# +#------------------------------------------------- + +include($$PWD/../TestUtils/test_common.pri) + +QT += testlib +QT -= gui + +TARGET = tst_parsertest +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + + +SOURCES += tst_parsertest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/SQLiteStudio3/Tests/ParserTest/tst_parsertest.cpp b/SQLiteStudio3/Tests/ParserTest/tst_parsertest.cpp new file mode 100644 index 0000000..5eb96a8 --- /dev/null +++ b/SQLiteStudio3/Tests/ParserTest/tst_parsertest.cpp @@ -0,0 +1,357 @@ +#include "parser/parser.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/keywords.h" +#include "parser/lexer.h" +#include "parser/parsererror.h" +#include +#include +#include + +class ParserTest : public QObject +{ + Q_OBJECT + + public: + ParserTest(); + + private: + Parser* parser2 = nullptr; + Parser* parser3 = nullptr; + + private Q_SLOTS: + void testGetTableTokens(); + void testGetTableTokens2(); + void testGetDatabaseTokens(); + void testGetFullObjects(); + void testGetFullObjects2(); + void testUnfinishedSingleSourceWithTolerance(); + void testCommentEnding1(); + void testCommentEnding2(); + void testOper1(); + void testBig1(); + void testTableFk(); + void testDoubleQuotes(); + void testInsertError(); + void testExpr(); + void testCommentBeginMultiline(); + void initTestCase(); + void cleanupTestCase(); +}; + +ParserTest::ParserTest() +{ +} + +void ParserTest::testGetTableTokens() +{ + QString sql = "select someTable.* FROM someTable;"; + + parser3->parse(sql); + QVERIFY(parser3->getErrors().size() == 0); + + SqliteQueryPtr query = parser3->getQueries()[0]; + TokenList tokens = query->getContextTableTokens(); + QVERIFY(tokens.size() == 2); + QVERIFY(tokens[0]->type == Token::OTHER); + QVERIFY(tokens[1]->type == Token::OTHER); +} + +void ParserTest::testGetTableTokens2() +{ + QString sql = "select db.tab.col FROM someTable;"; + + parser3->parse(sql); + QVERIFY(parser3->getErrors().size() == 0); + + SqliteQueryPtr query = parser3->getQueries()[0]; + TokenList tokens = query->getContextTableTokens(); + QVERIFY(tokens.size() == 2); + QVERIFY(tokens[0]->type == Token::OTHER); + QVERIFY(tokens[1]->type == Token::OTHER); +} + +void ParserTest::testGetDatabaseTokens() +{ + QString sql = "select * FROM someDb.[table];"; + + parser3->parse(sql); + QVERIFY(parser3->getErrors().size() == 0); + + SqliteQueryPtr query = parser3->getQueries()[0]; + TokenList tokens = query->getContextTableTokens(); + QVERIFY(tokens.size() == 1); + QVERIFY(tokens[0]->type == Token::OTHER); +} + +void ParserTest::testGetFullObjects() +{ + QString sql = "select col FROM someDb.[table];"; + + parser3->parse(sql); + QVERIFY(parser3->getErrors().size() == 0); + + SqliteQueryPtr query = parser3->getQueries()[0]; + QList fullObjects = query->getContextFullObjects(); + QVERIFY(fullObjects.size() == 2); + + foreach (const SqliteStatement::FullObject& fullObj, fullObjects) + { + switch (fullObj.type) + { + case SqliteStatement::FullObject::TABLE: + QVERIFY(fullObj.database && fullObj.database->value == "someDb"); + QVERIFY(fullObj.object && fullObj.object->value == "[table]"); + break; + case SqliteStatement::FullObject::DATABASE: + QVERIFY(fullObj.database && fullObj.database->value == "someDb"); + break; + default: + QFAIL("Unexpected FullObject type."); + } + } +} + +void ParserTest::testGetFullObjects2() +{ + QString sql = "select col, tab2.*, abcDb.abcTab.abcCol FROM someDb.[table];"; + + parser3->parse(sql); + QVERIFY(parser3->getErrors().size() == 0); + + SqliteQueryPtr query = parser3->getQueries()[0]; + QList fullObjects = query->getContextFullObjects(); + QVERIFY(fullObjects.size() == 5); + + foreach (const SqliteStatement::FullObject& fullObj, fullObjects) + { + switch (fullObj.type) + { + case SqliteStatement::FullObject::TABLE: + { + QVERIFY(fullObj.object); + if (fullObj.database && fullObj.database->value == "someDb") + QVERIFY(fullObj.object->value == "[table]"); + else if (fullObj.database && fullObj.database->value == "abcDb") + QVERIFY(fullObj.object->value == "abcTab"); + else if (!fullObj.database) + QVERIFY(fullObj.object->value == "tab2"); + else + QFAIL("Invalid TABLE full object."); + + break; + } + case SqliteStatement::FullObject::DATABASE: + { + if (fullObj.database) + QVERIFY(fullObj.database->value == "someDb" || fullObj.database->value == "abcDb"); + else + QFAIL("Invalid DATABASE full object."); + + break; + } + default: + QFAIL("Unexpected FullObject type."); + } + } +} + +void ParserTest::testUnfinishedSingleSourceWithTolerance() +{ + QString sql = "SELECT * FROM test.;"; + bool res = parser3->parse(sql, true); + QVERIFY(res); +} + +void ParserTest::testCommentEnding1() +{ + QString sql = "select 1 --aaa"; + bool res = parser3->parse(sql); + QVERIFY(res); +} + +void ParserTest::testCommentEnding2() +{ + QString sql = "select 1 /*aaa"; + bool res = parser3->parse(sql); + QVERIFY(res); +} + +void ParserTest::testOper1() +{ + QString sql = "SELECT dfgd<=2"; + TokenList tokens = Lexer::tokenize(sql, Dialect::Sqlite3); + QVERIFY(tokens[2]->value == "dfgd"); + QVERIFY(tokens[3]->value == "<="); + QVERIFY(tokens[4]->value == "2"); +} + +void ParserTest::testBig1() +{ + QString sql = "select " + "''|| " + "''|| " + "''|| " + "''|| " + "''|| " + "''|| " + "''|| " + "'Ð�TH;°Ñ禅TH;°Ð»Ñ즅TH;½Ð¸Ðº Ñ㦅TH;¿ÑঅTH;°Ð²Ð»ÐµÐ½Ð¸Ñ怜t;/P1>'|| " + "''|| " + "''||strftime('%d.%m.%Y',d.demdate)||''|| " + "'4642'||substr('000000'||d.id, -6, 6)||''|| " + "''|| " + "''||ins.fullname||''|| " + "''||ins.shortname||''|| " + "'0'||ins.regnum||''|| " + "''|| " + "''||ins.inn||''|| " + "''||ins.kpp||''|| " + "''||ins.addr||''|| " + "''||ins.postaddr||''|| " + "''||round(dem_s+dem_n+dem_f+dem_t,2)||''|| " + "''||strftime('%d.%m.%Y',d.demdate,'+15 day')||''|| " + "'0,00'|| " + "'0,00'|| " + "''|| " + "'0,00'|| " + "''|| " + "'0,00'|| " + "'0,00'|| " + "''|| " + "'0,00'|| " + "''|| " + "'0,00'|| " + "'0,00'|| " + "''||round(dem_s+dem_n+dem_f+dem_t,2)||''|| " + "''||round(dem_s+dem_n,2)||''|| " + "'0,00'|| " + "''||round(dem_s,2)||''|| " + "''||case when d.dem_s>0 then '(ÐꦅTH;ᦅTH;ꠧ'||kbk.kbk_s||')' else '' end || ''|| " + "''||round(dem_n,2)||''|| " + "''||case when d.dem_n>0 then '(ÐꦅTH;ᦅTH;ꠧ'||kbk.kbk_n||')' else '' end||''|| " + "''||round(dem_f,2)||''|| " + "''||case when d.dem_f>0 then '(ÐꦅTH;ᦅTH;ꠧ'||kbk.kbk_f||')' else '' end||''|| " + "''||round(dem_t,2)||''|| " + "''||case when d.dem_t>0 then '(ÐꦅTH;ᦅTH;ꠧ'||kbk.kbk_t||')' else '' end||''|| " + "'0,00'|| " + "'0,00 0,00 0,00 0,00'|| " + "'Ñ঎tilde;㦅TH;±.;'|| " + "'0,00'|| " + "''|| " + "'0,00'|| " + "''|| " + "'0,00'|| " + "''|| " + "'0,00'|| " + "''|| " + "'0,00'|| " + "''|| " + "''|| " + "''|| " + "''||strftime('%d.%m.%Y',d.demdate)||''|| " + "'0'|| " + "''|| " + "'0'|| " + "''|| " + "''||ins.Specname||''|| " + "''|| " + "'' " + "from demands d " + "left join ins on ins.regnum=d.regnum " + "left join kbk on kbk.cat=ins.Cat " + "limit 1"; + + bool res = parser3->parse(sql); + if (!res) + { + qWarning() << parser3->getErrorString(); + ParserError* error = parser3->getErrors().first(); + qDebug() << "Error starts at:" << sql.mid(error->getFrom()); + } + + QVERIFY(res); +} + +void ParserTest::testTableFk() +{ + QString sql = "CREATE TABLE test (id INTEGER, FOREIGN KEY (id) REFERENCES test2 (id2));"; + + parser3->parse(sql); + QVERIFY(parser3->getErrors().size() == 0); + + SqliteQueryPtr query = parser3->getQueries()[0]; + SqliteCreateTablePtr creatrTable = query.dynamicCast(); + QVERIFY(creatrTable->constraints.size() == 1); + QVERIFY(creatrTable->constraints.first()->indexedColumns.size() == 1); + QVERIFY(creatrTable->constraints.first()->indexedColumns.first()->name == "id"); + QVERIFY(creatrTable->constraints.first()->type == SqliteCreateTable::Constraint::FOREIGN_KEY); + QVERIFY(creatrTable->constraints.first()->foreignKey != nullptr); + QVERIFY(creatrTable->constraints.first()->foreignKey->foreignTable == "test2"); + QVERIFY(creatrTable->constraints.first()->foreignKey->indexedColumns.size() == 1); + QVERIFY(creatrTable->constraints.first()->foreignKey->indexedColumns.first()->name == "id2"); +} + +void ParserTest::testDoubleQuotes() +{ + QString sql = "select \"1\""; + bool res = parser3->parse(sql); + QVERIFY(res); + QVERIFY(parser3->getQueries().size() > 0); + + SqliteQueryPtr query = parser3->getQueries().first(); + QVERIFY(query); + + SqliteSelectPtr select = query.dynamicCast(); + QVERIFY(select); + QVERIFY(select->coreSelects.size() > 0); + QVERIFY(select->coreSelects.first()->resultColumns.size() > 0); + + SqliteSelect::Core::ResultColumn* rc = select->coreSelects.first()->resultColumns.first(); + SqliteExpr* e = rc->expr; + QVERIFY(e); + + QVERIFY(e->mode == SqliteExpr::Mode::ID); + QVERIFY(e->possibleDoubleQuotedString); +} + +void ParserTest::testInsertError() +{ + QString sql = "INSERT INTO test "; + bool res = parser3->parse(sql); + QVERIFY(!res); + QVERIFY(parser3->getErrors().size() == 1); +} + +void ParserTest::testExpr() +{ + QString sql = "CAST (CASE WHEN port REGEXP '^[A-Z]' THEN substr(port, 2) ELSE port END AS INT) AS port"; + SqliteExpr* expr = parser3->parseExpr(sql); + QVERIFY(expr); +} + +void ParserTest::testCommentBeginMultiline() +{ + QString sql = "/*"; + TokenList tokens = Lexer::tokenize(sql, Dialect::Sqlite3); + QVERIFY(tokens.size() == 1); + QVERIFY(tokens[0]->type == Token::COMMENT); +} + +void ParserTest::initTestCase() +{ + initKeywords(); + Lexer::staticInit(); + parser2 = new Parser(Dialect::Sqlite2); + parser3 = new Parser(Dialect::Sqlite3); +} + +void ParserTest::cleanupTestCase() +{ + delete parser2; + delete parser3; +} + +QTEST_APPLESS_MAIN(ParserTest) + +#include "tst_parsertest.moc" diff --git a/SQLiteStudio3/Tests/SelectResolverTest/SelectResolverTest.pro b/SQLiteStudio3/Tests/SelectResolverTest/SelectResolverTest.pro new file mode 100644 index 0000000..d533c7e --- /dev/null +++ b/SQLiteStudio3/Tests/SelectResolverTest/SelectResolverTest.pro @@ -0,0 +1,19 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-04-05T00:10:49 +# +#------------------------------------------------- + +include($$PWD/../TestUtils/test_common.pri) + +QT += testlib +QT -= gui + +TARGET = tst_selectresolvertest +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += tst_selectresolvertest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/SQLiteStudio3/Tests/SelectResolverTest/tst_selectresolvertest.cpp b/SQLiteStudio3/Tests/SelectResolverTest/tst_selectresolvertest.cpp new file mode 100644 index 0000000..969ebad --- /dev/null +++ b/SQLiteStudio3/Tests/SelectResolverTest/tst_selectresolvertest.cpp @@ -0,0 +1,248 @@ +#include "selectresolver.h" +#include "db/db.h" +#include "parser/keywords.h" +#include "parser/lexer.h" +#include "parser/parser.h" +#include "parser/parser.h" +#include "dbsqlite3mock.h" +#include "mocks.h" +#include "parser/parser.h" +#include +#include +#include + +class SelectResolverTest : public QObject +{ + Q_OBJECT + + public: + SelectResolverTest(); + + private: + Db* db = nullptr; + + private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testTableHash(); + void testColumnHash(); + void testWithCommonTableExpression(); + void testStarWithJoinAndError(); + void test1(); +}; + +SelectResolverTest::SelectResolverTest() +{ +} + +void SelectResolverTest::testTableHash() +{ + QSet tables; + + SelectResolver::Table t1; + t1.database = "d1"; + t1.table = "t1"; + t1.alias = "a1"; + tables << t1; + + // different alias and database + SelectResolver::Table t2; + t2.database = "d2"; + t2.table = "t1"; + t2.alias = QString::null; + tables << t2; + + // different database + SelectResolver::Table t3; + t3.database = "d2"; + t3.table = "t1"; + t3.alias = "a1"; + tables << t3; + + // same as t3 + SelectResolver::Table t4; + t4.database = "d2"; + t4.table = "t1"; + t4.alias = "a1"; + tables << t4; + + // all null + SelectResolver::Table t5; + tables << t5; + + // same as t5 + SelectResolver::Table t6; + tables << t6; + + // similar to t1, but different database + SelectResolver::Table t7; + t7.database = "x"; + t7.table = "t1"; + t7.alias = "a1"; + tables << t7; + + // similar to t1, but different table + SelectResolver::Table t8; + t8.database = "d1"; + t8.table = "x"; + t8.alias = "a1"; + tables << t8; + + // similar to t1, but different alias + SelectResolver::Table t9; + t9.database = "d1"; + t9.table = "t1"; + t9.alias = "x"; + tables << t9; + + QVERIFY(tables.size() == 7); +} + +void SelectResolverTest::testColumnHash() +{ + QSet columns; + + SelectResolver::Column c1; + c1.database = "d1"; + c1.table = "t1"; + c1.column = "c1"; + c1.alias = "a1"; + c1.tableAlias = "ta1"; + c1.displayName = "d1"; + c1.type = SelectResolver::Column::COLUMN; + columns << c1; + + // This should be treated as equal to c1. + SelectResolver::Column c2; + c2.database = "d1"; + c2.table = "t1"; + c2.column = "c1"; + c2.alias = "x"; + c2.tableAlias = "ta1"; + c2.displayName = "x"; + c2.type = SelectResolver::Column::OTHER; + columns << c2; + + // Different database + SelectResolver::Column c3; + c3.database = "x"; + c3.table = "t1"; + c3.column = "c1"; + c3.alias = "x"; + c3.tableAlias = "ta1"; + c3.displayName = "x"; + c3.type = SelectResolver::Column::OTHER; + columns << c3; + + // Different table + SelectResolver::Column c4; + c4.database = "d1"; + c4.table = "x"; + c4.column = "c1"; + c4.alias = "x"; + c4.tableAlias = "ta1"; + c4.displayName = "x"; + c4.type = SelectResolver::Column::OTHER; + columns << c4; + + // Different column + SelectResolver::Column c5; + c5.database = "d1"; + c5.table = "t1"; + c5.column = "x"; + c5.alias = "x"; + c5.tableAlias = "ta1"; + c5.displayName = "x"; + c5.type = SelectResolver::Column::OTHER; + columns << c5; + + // Different table alias + SelectResolver::Column c6; + c6.database = "d1"; + c6.table = "t1"; + c6.column = "c1"; + c6.alias = "x"; + c6.tableAlias = "x"; + c6.displayName = "x"; + c6.type = SelectResolver::Column::OTHER; + columns << c6; + + QVERIFY(columns.size() == 5); +} + +void SelectResolverTest::testWithCommonTableExpression() +{ + // Test with query from examples from SQLite documentation + QString sql = "WITH RECURSIVE works_for_alice(n) AS (" + " VALUES('Alice')" + " UNION" + " SELECT name" + " FROM org, works_for_alice" + " WHERE org.boss = works_for_alice.n" + " )" + " SELECT avg(height)" + " FROM org" + " WHERE org.name IN works_for_alice"; + + SelectResolver resolver(db, sql); + Parser parser(db->getDialect()); + QVERIFY(parser.parse(sql)); + + QList> columns = resolver.resolve(parser.getQueries().first().dynamicCast().data()); + QList coreColumns = columns.first(); + QVERIFY(coreColumns.size() == 1); + QVERIFY(coreColumns[0].type == SelectResolver::Column::OTHER); + QVERIFY(coreColumns[0].flags & SelectResolver::Flag::FROM_CTE_SELECT); +} + +void SelectResolverTest::testStarWithJoinAndError() +{ + QString sql = "SELECT t1.*, t2.* FROM test t1 JOIN test2 USING (col1)"; + SelectResolver resolver(db, sql); + Parser parser(db->getDialect()); + QVERIFY(parser.parse(sql)); + + QList > columns = resolver.resolve(parser.getQueries().first().dynamicCast().data()); + QVERIFY(columns.first().size() == 3); + QVERIFY(resolver.hasErrors()); +} + +void SelectResolverTest::test1() +{ + QString sql = "SELECT * FROM (SELECT count(col1), col2 FROM test)"; + SelectResolver resolver(db, sql); + Parser parser(db->getDialect()); + QVERIFY(parser.parse(sql)); + + QList > columns = resolver.resolve(parser.getQueries().first().dynamicCast().data()); + QList coreColumns = columns.first(); + QVERIFY(coreColumns[0].type == SelectResolver::Column::OTHER); + QVERIFY(coreColumns[1].type == SelectResolver::Column::COLUMN); + QVERIFY(coreColumns[1].table == "test"); + QVERIFY(coreColumns[1].column == "col2"); +} + +void SelectResolverTest::initTestCase() +{ + initKeywords(); + Lexer::staticInit(); + initMocks(); + + db = new DbSqlite3Mock("testdb"); + db->open(); + db->exec("CREATE TABLE test (col1, col2, col3);"); + db->exec("CREATE TABLE org (name TEXT PRIMARY KEY, boss TEXT REFERENCES org, height INT)"); + db->exec("CREATE TABLE test2 (col1);"); + //SqlQueryPtr results = db->exec("SELECT name FROM sqlite_master"); +} + +void SelectResolverTest::cleanupTestCase() +{ + db->close(); + delete db; + db = nullptr; +} + +QTEST_APPLESS_MAIN(SelectResolverTest) + +#include "tst_selectresolvertest.moc" diff --git a/SQLiteStudio3/Tests/TableModifierTest/TableModifierTest.pro b/SQLiteStudio3/Tests/TableModifierTest/TableModifierTest.pro new file mode 100644 index 0000000..ed0f6f7 --- /dev/null +++ b/SQLiteStudio3/Tests/TableModifierTest/TableModifierTest.pro @@ -0,0 +1,19 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-11-23T15:08:59 +# +#------------------------------------------------- + +include($$PWD/../TestUtils/test_common.pri) + +QT += testlib +QT -= gui + +TARGET = tst_tablemodifiertest +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += tst_tablemodifiertest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/SQLiteStudio3/Tests/TableModifierTest/tst_tablemodifiertest.cpp b/SQLiteStudio3/Tests/TableModifierTest/tst_tablemodifiertest.cpp new file mode 100644 index 0000000..2630f36 --- /dev/null +++ b/SQLiteStudio3/Tests/TableModifierTest/tst_tablemodifiertest.cpp @@ -0,0 +1,266 @@ +#include "parser/keywords.h" +#include "parser/lexer.h" +#include "tablemodifier.h" +#include "parser/parser.h" +#include "db/db.h" +#include "dbsqlite3mock.h" +#include "mocks.h" +#include +#include +#include + +class TableModifierTest : public QObject +{ + Q_OBJECT + + public: + TableModifierTest(); + + private: + void verifyRe(const QString& re, const QString& sql, Qt::CaseSensitivity cs = Qt::CaseSensitive); + + Db* db = nullptr; + static const constexpr char* mainTableDdl = "CREATE TABLE test (id int, val text, val2 text);"; + SqliteCreateTablePtr createTable; + + private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testCase1(); + void testCase2(); + void testCase3(); + void testCase4(); + void testCase5(); + void testCase6(); +}; + +TableModifierTest::TableModifierTest() +{ +} + +void TableModifierTest::verifyRe(const QString& re, const QString& sql, Qt::CaseSensitivity cs) +{ + QRegExp regExp(re, cs); + QVERIFY2(regExp.exactMatch(sql), QString("Failed RegExp validation:\n%1\nfor SQL:\n%2").arg(re).arg(sql).toLatin1().data()); +} + +void TableModifierTest::testCase1() +{ + db->exec("CREATE TABLE abc (id int, xyz text REFERENCES test (val));"); + + TableModifier mod(db, "test"); + createTable->table = "test2"; + mod.alterTable(createTable); + QStringList sqls = mod.generateSqls(); + + /* + * 1. Create new (with new name) + * 2. Copy data to new + * 3. Drop old table. + * 4. Rename referencing table to temp name. + * 5. Create new referencing table. + * 6. Copy data to new referencing table. + * 7. Drop temp table. + */ + QVERIFY(sqls.size() == 7); + int i = 0; + verifyRe("CREATE TABLE test2 .*", sqls[i++]); + verifyRe("INSERT INTO test2.*SELECT.*FROM test;", sqls[i++]); + verifyRe("DROP TABLE test;", sqls[i++]); + verifyRe("ALTER TABLE abc RENAME TO sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("CREATE TABLE abc .*", sqls[i++]); + verifyRe("INSERT INTO abc.*SELECT.*FROM sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("DROP TABLE sqlitestudio_temp_table.*", sqls[i++]); +} + +void TableModifierTest::testCase2() +{ + db->exec("CREATE TABLE abc (id int, xyz text REFERENCES test (val));"); + + TableModifier mod(db, "test"); + createTable->columns[1]->name = "newCol"; + mod.alterTable(createTable); + QStringList sqls = mod.generateSqls(); + + /* + * 1. Rename to temp. + * 2. Create new. + * 3. Copy data from temp to new one. + * 4. Drop temp table. + * 5. Rename referencing table to temp name. + * 6. Create new referencing table. + * 7. Copy data to new referencing table. + * 8. Drop temp table. + */ + QVERIFY(sqls.size() == 8); + int i = 0; + verifyRe("ALTER TABLE test RENAME TO sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("CREATE TABLE test .*newCol.*", sqls[i++]); + verifyRe("INSERT INTO test.*SELECT.*FROM sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("DROP TABLE sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("ALTER TABLE abc RENAME TO sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("CREATE TABLE abc .*xyz text REFERENCES test \\(newCol\\).*", sqls[i++]); + verifyRe("INSERT INTO abc.*SELECT.*FROM sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("DROP TABLE sqlitestudio_temp_table.*", sqls[i++]); +} + +void TableModifierTest::testCase3() +{ + db->exec("CREATE TABLE abc (id int, xyz text REFERENCES test (val));"); + db->exec("CREATE INDEX i1 ON test (val);"); + db->exec("CREATE INDEX i2 ON abc (id);"); + + TableModifier mod(db, "test"); + createTable->table = "newTable"; + createTable->columns[1]->name = "newCol"; + mod.alterTable(createTable); + QStringList sqls = mod.generateSqls(); + + /* + * 1. Create new (with new name) + * 2. Copy data to new + * 3. Drop old table. + * 4. Rename referencing table to temp name. + * 5. Create new referencing table. + * 6. Copy data to new referencing table. + * 7. Drop temp table. + * 8. Re-create index i2. + * 9. Re-create index i1 with new table name and column name. + */ + QVERIFY(sqls.size() == 9); + int i = 0; + verifyRe("CREATE TABLE newTable .*", sqls[i++]); + verifyRe("INSERT INTO newTable.*SELECT.*FROM test;", sqls[i++]); + verifyRe("DROP TABLE test;", sqls[i++]); + verifyRe("ALTER TABLE abc RENAME TO sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("CREATE TABLE abc .*xyz text REFERENCES newTable \\(newCol\\).*", sqls[i++]); + verifyRe("INSERT INTO abc.*SELECT.*FROM sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("DROP TABLE sqlitestudio_temp_table.*", sqls[i++]); + verifyRe("CREATE INDEX i2 ON abc \\(id\\);", sqls[i++]); + verifyRe("CREATE INDEX i1 ON newTable \\(newCol\\);", sqls[i++]); +} + +void TableModifierTest::testCase4() +{ + db->exec("CREATE TRIGGER t1 AFTER UPDATE OF Val ON Test BEGIN " + "SELECT * FROM (SELECT Val FROM Test); " + "UPDATE Test SET Val = (SELECT Val FROM Test) WHERE x = (SELECT Val FROM Test); " + "INSERT INTO Test (val) VALUES (1); " + "END;"); + + TableModifier mod(db, "test"); + createTable->table = "newTable"; + createTable->columns[1]->name = "newCol"; + mod.alterTable(createTable); + QStringList sqls = mod.generateSqls(); + + /* + * 1. Create new (with new name) + * 2. Copy data to new + * 3. Drop old table. + * 4. Recreate trigger with all subqueries updated. + */ + QVERIFY(sqls.size() == 4); + int i = 0; + verifyRe("CREATE TABLE newTable .*", sqls[i++]); + verifyRe("INSERT INTO newTable.*SELECT.*FROM test;", sqls[i++]); + verifyRe("DROP TABLE test;", sqls[i++]); + QVERIFY2("CREATE TRIGGER t1 AFTER UPDATE OF newCol ON newTable " + "BEGIN " + "SELECT * FROM (SELECT newCol FROM newTable); " + "UPDATE newTable SET newCol = (SELECT newCol FROM newTable) WHERE x = (SELECT newCol FROM newTable); " + "INSERT INTO newTable (newCol) VALUES (1); " + "END;" == sqls[i++], "Trigger DDL incorrect."); +} + +void TableModifierTest::testCase5() +{ + db->exec("CREATE VIEW v1 AS SELECT * FROM (SELECT Val FROM Test);"); + db->exec("CREATE TRIGGER t1 INSTEAD OF INSERT ON v1 BEGIN SELECT 1; END;"); + db->exec("CREATE TRIGGER t2 AFTER INSERT ON v1 BEGIN SELECT 1; END;"); + db->exec("CREATE TRIGGER t3 INSTEAD OF INSERT ON test BEGIN SELECT 1; END;"); + + TableModifier mod(db, "test"); + createTable->table = "newTable"; + createTable->columns[1]->name = "newCol"; + mod.alterTable(createTable); + QStringList sqls = mod.generateSqls(); + + /* + * 1. Create new (with new name) + * 2. Copy data to new + * 3. Drop old table. + * 4. Drop old view. + * 5. Recreate view with new column and table. + * 6. Recreate trigger with all subqueries updated. + */ + QVERIFY(sqls.size() == 6); + int i = 0; + verifyRe("CREATE TABLE newTable .*", sqls[i++]); + verifyRe("INSERT INTO newTable.*SELECT.*FROM test;", sqls[i++]); + verifyRe("DROP TABLE test;", sqls[i++]); + verifyRe("DROP VIEW v1;", sqls[i++]); + verifyRe("CREATE VIEW v1 AS SELECT \\* FROM \\(SELECT newCol FROM newTable\\);", sqls[i++]); + verifyRe("CREATE TRIGGER t1 INSTEAD OF INSERT ON v1 BEGIN SELECT 1; END;", sqls[i++]); +} + +void TableModifierTest::testCase6() +{ + db->exec("CREATE VIEW v1 AS SELECT * FROM (SELECT Id, Val FROM Test);"); + db->exec("CREATE TRIGGER t2 AFTER UPDATE OF Id, Val ON Test BEGIN SELECT Val, Val2 FROM Test; END;"); + + TableModifier mod(db, "test"); + createTable->table = "newTable"; + createTable->columns.removeAt(1); + mod.alterTable(createTable); + QStringList sqls = mod.generateSqls(); + + /* + * 1. Create new (with new name) + * 2. Copy data to new + * 3. Drop old table. + * 4. Recreate trigger with all subqueries updated. + */ + QVERIFY(sqls.size() == 6); + int i = 0; + verifyRe("CREATE TABLE newTable \\(id int, val2 text\\);", sqls[i++]); + verifyRe("INSERT INTO newTable \\(id, val2\\) SELECT id, val2 FROM test;", sqls[i++]); + verifyRe("DROP TABLE test;", sqls[i++]); + verifyRe("CREATE TRIGGER t2 AFTER UPDATE OF Id ON newTable BEGIN SELECT NULL, Val2 FROM newTable; END;", sqls[i++]); + verifyRe("DROP VIEW v1;", sqls[i++]); + verifyRe("CREATE VIEW v1 AS SELECT \\* FROM \\(SELECT Id, NULL FROM newTable\\);", sqls[i++]); +} + +void TableModifierTest::initTestCase() +{ + initKeywords(); + Lexer::staticInit(); +} + +void TableModifierTest::init() +{ + initMocks(); + + db = new DbSqlite3Mock("testdb"); + db->open(); + db->exec(mainTableDdl); + + Parser parser(db->getDialect()); + Q_ASSERT(parser.parse(mainTableDdl)); + Q_ASSERT(parser.getQueries().size() > 0); + createTable = parser.getQueries().first().dynamicCast(); + Q_ASSERT(!createTable.isNull()); +} + +void TableModifierTest::cleanup() +{ + db->close(); + delete db; + db = nullptr; +} + + +QTEST_APPLESS_MAIN(TableModifierTest) + +#include "tst_tablemodifiertest.moc" diff --git a/SQLiteStudio3/Tests/TestUtils/TestUtils.pro b/SQLiteStudio3/Tests/TestUtils/TestUtils.pro new file mode 100644 index 0000000..26600e1 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/TestUtils.pro @@ -0,0 +1,52 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-03-29T23:25:17 +# +#------------------------------------------------- + +include(test_common.pri) + +QT -= gui + +TARGET = TestUtils +TEMPLATE = lib + +QMAKE_CXXFLAGS += -std=c++11 + +DEFINES += TESTUTILS_LIBRARY + +SOURCES += \ + dbsqlite3mock.cpp \ + functionmanagermock.cpp \ + pluginmanagermock.cpp \ + configmock.cpp \ + mocks.cpp \ + dbattachermock.cpp \ + dbmanagermock.cpp \ + collationmanagermock.cpp + +HEADERS +=\ + testutils_global.h \ + dbsqlite3mock.h \ + hippomocks.h \ + functionmanagermock.h \ + pluginmanagermock.h \ + configmock.h \ + mocks.h \ + dbattachermock.h \ + dbmanagermock.h \ + collationmanagermock.h + +unix:!symbian { + maemo5 { + target.path = /opt/usr/lib + } else { + target.path = /usr/lib + } + INSTALLS += target +} + +LIBS += -lcoreSQLiteStudio -lsqlite3 + +OTHER_FILES += \ + test_common.pri diff --git a/SQLiteStudio3/Tests/TestUtils/collationmanagermock.cpp b/SQLiteStudio3/Tests/TestUtils/collationmanagermock.cpp new file mode 100644 index 0000000..640a252 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/collationmanagermock.cpp @@ -0,0 +1,29 @@ +#include "collationmanagermock.h" + +CollationManagerMock::CollationManagerMock() +{ +} + +void CollationManagerMock::setCollations(const QList&) +{ +} + +QList CollationManagerMock::getAllCollations() const +{ + return QList(); +} + +QList CollationManagerMock::getCollationsForDatabase(const QString&) const +{ + return QList(); +} + +int CollationManagerMock::evaluate(const QString&, const QString&, const QString&) +{ + return 0; +} + +int CollationManagerMock::evaluateDefault(const QString&, const QString&) +{ + return 0; +} diff --git a/SQLiteStudio3/Tests/TestUtils/collationmanagermock.h b/SQLiteStudio3/Tests/TestUtils/collationmanagermock.h new file mode 100644 index 0000000..eb08b7b --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/collationmanagermock.h @@ -0,0 +1,18 @@ +#ifndef COLLATIONMANAGERMOCK_H +#define COLLATIONMANAGERMOCK_H + +#include "services/collationmanager.h" + +class CollationManagerMock : public CollationManager +{ + public: + CollationManagerMock(); + + void setCollations(const QList&); + QList getAllCollations() const; + QList getCollationsForDatabase(const QString&) const; + int evaluate(const QString&, const QString&, const QString&); + int evaluateDefault(const QString&, const QString&); +}; + +#endif // COLLATIONMANAGERMOCK_H diff --git a/SQLiteStudio3/Tests/TestUtils/configmock.cpp b/SQLiteStudio3/Tests/TestUtils/configmock.cpp new file mode 100644 index 0000000..8b62dc2 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/configmock.cpp @@ -0,0 +1,206 @@ +#include "configmock.h" + +void ConfigMock::init() +{ +} + +void ConfigMock::cleanUp() +{ +} + +const QString& ConfigMock::getConfigDir() +{ + static const QString cfg; + return cfg; +} + +void ConfigMock::beginMassSave() +{ +} + +void ConfigMock::commitMassSave() +{ +} + +void ConfigMock::rollbackMassSave() +{ +} + +void ConfigMock::set(const QString&, const QString&, const QVariant&) +{ +} + +QVariant ConfigMock::get(const QString&, const QString&) +{ + return QVariant(); +} + +QHash ConfigMock::getAll() +{ + return QHash(); +} + +bool ConfigMock::addDb(const QString&, const QString&, const QHash&) +{ + return true; +} + +bool ConfigMock::updateDb(const QString&, const QString&, const QString&, const QHash&) +{ + return true; +} + +bool ConfigMock::removeDb(const QString&) +{ + return true; +} + +bool ConfigMock::isDbInConfig(const QString&) +{ + return true; +} + +QString ConfigMock::getLastErrorString() const +{ + return QString(); +} + +QList ConfigMock::dbList() +{ + return QList(); +} + +Config::CfgDbPtr ConfigMock::getDb(const QString&) +{ + return Config::CfgDbPtr(); +} + +void ConfigMock::storeGroups(const QList&) +{ +} + +QList ConfigMock::getGroups() +{ + return QList(); +} + +Config::DbGroupPtr ConfigMock::getDbGroup(const QString&) +{ + return Config::DbGroupPtr(); +} + +qint64 ConfigMock::addSqlHistory(const QString&, const QString&, int, int) +{ + return 0; +} + +void ConfigMock::updateSqlHistory(qint64, const QString&, const QString&, int, int) +{ +} + +void ConfigMock::clearSqlHistory() +{ +} + +QAbstractItemModel* ConfigMock::getSqlHistoryModel() +{ + return nullptr; +} + +void ConfigMock::addCliHistory(const QString&) +{ +} + +void ConfigMock::applyCliHistoryLimit() +{ +} + +void ConfigMock::clearCliHistory() +{ +} + +QStringList ConfigMock::getCliHistory() const +{ + return QStringList(); +} + +void ConfigMock::addDdlHistory(const QString&, const QString&, const QString&) +{ +} + +QList ConfigMock::getDdlHistoryFor(const QString&, const QString&, const QDate&) +{ + return QList(); +} + +DdlHistoryModel* ConfigMock::getDdlHistoryModel() +{ + return nullptr; +} + +void ConfigMock::clearDdlHistory() +{ +} + +void ConfigMock::begin() +{ +} + +void ConfigMock::commit() +{ +} + +void ConfigMock::rollback() +{ +} + +bool ConfigMock::setCollations(const QList&) +{ + return true; +} + +QList ConfigMock::getCollations() const +{ + return QList(); +} + +const QString &ConfigMock::getConfigDir() const +{ + static QString s; + return s; +} + +QString ConfigMock::getConfigFilePath() const +{ + return QString(); +} + +bool ConfigMock::isMassSaving() const +{ + return false; +} + +void ConfigMock::addReportHistory(bool, const QString &, const QString &) +{ +} + +QList ConfigMock::getReportHistory() +{ + return QList(); +} + +void ConfigMock::deleteReport(int) +{ +} + +void ConfigMock::clearReportHistory() +{ +} + +void ConfigMock::refreshSqlHistory() +{ +} + +void ConfigMock::refreshDdlHistory() +{ +} diff --git a/SQLiteStudio3/Tests/TestUtils/configmock.h b/SQLiteStudio3/Tests/TestUtils/configmock.h new file mode 100644 index 0000000..2115ccf --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/configmock.h @@ -0,0 +1,60 @@ +#ifndef CONFIGMOCK_H +#define CONFIGMOCK_H + +#include "services/config.h" + +#include + +class ConfigMock : public Config +{ + public: + void init(); + void cleanUp(); + const QString& getConfigDir(); + void beginMassSave(); + void commitMassSave(); + void rollbackMassSave(); + void set(const QString&, const QString&, const QVariant&); + QVariant get(const QString&, const QString&); + QHash getAll(); + bool addDb(const QString&, const QString&, const QHash&); + bool updateDb(const QString&, const QString&, const QString&, const QHash&); + bool removeDb(const QString&); + bool isDbInConfig(const QString&); + QString getLastErrorString() const; + QList dbList(); + CfgDbPtr getDb(const QString&); + void storeGroups(const QList&); + QList getGroups(); + DbGroupPtr getDbGroup(const QString&); + qint64 addSqlHistory(const QString&, const QString&, int, int); + void updateSqlHistory(qint64, const QString&, const QString&, int, int); + void clearSqlHistory(); + QAbstractItemModel*getSqlHistoryModel(); + void addCliHistory(const QString&); + void applyCliHistoryLimit(); + void clearCliHistory(); + QStringList getCliHistory() const; + void addDdlHistory(const QString&, const QString&, const QString&); + QList getDdlHistoryFor(const QString&, const QString&, const QDate&); + DdlHistoryModel* getDdlHistoryModel(); + void clearDdlHistory(); + void begin(); + void commit(); + void rollback(); + bool setCollations(const QList&); + QList getCollations() const; + const QString &getConfigDir() const; + QString getConfigFilePath() const; + bool isMassSaving() const; + void addReportHistory(bool, const QString &, const QString &); + QList getReportHistory(); + void deleteReport(int); + void clearReportHistory(); + + public slots: + void refreshSqlHistory(); + void refreshDdlHistory(); +}; + +#endif // CONFIGMOCK_H diff --git a/SQLiteStudio3/Tests/TestUtils/dbattachermock.cpp b/SQLiteStudio3/Tests/TestUtils/dbattachermock.cpp new file mode 100644 index 0000000..9f61d2d --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/dbattachermock.cpp @@ -0,0 +1,35 @@ +#include "dbattachermock.h" + +bool DbAttacherMock::attachDatabases(const QString&) +{ + return true; +} + +bool DbAttacherMock::attachDatabases(const QList&) +{ + return true; +} + +bool DbAttacherMock::attachDatabases(SqliteQueryPtr) +{ + return true; +} + +void DbAttacherMock::detachDatabases() +{ +} + +BiStrHash DbAttacherMock::getDbNameToAttach() const +{ + return BiStrHash(); +} + +QString DbAttacherMock::getQuery() const +{ + return QString(); +} + +DbAttacher* DbAttacherFactoryMock::create(Db*) +{ + return new DbAttacherMock(); +} diff --git a/SQLiteStudio3/Tests/TestUtils/dbattachermock.h b/SQLiteStudio3/Tests/TestUtils/dbattachermock.h new file mode 100644 index 0000000..b3ab345 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/dbattachermock.h @@ -0,0 +1,23 @@ +#ifndef DBATTACHERMOCK_H +#define DBATTACHERMOCK_H + +#include "dbattacher.h" + +class DbAttacherMock : public DbAttacher +{ + public: + bool attachDatabases(const QString&); + bool attachDatabases(const QList&); + bool attachDatabases(SqliteQueryPtr); + void detachDatabases(); + BiStrHash getDbNameToAttach() const; + QString getQuery() const; +}; + +class DbAttacherFactoryMock : public DbAttacherFactory +{ + public: + DbAttacher* create(Db*); +}; + +#endif // DBATTACHERMOCK_H diff --git a/SQLiteStudio3/Tests/TestUtils/dbmanagermock.cpp b/SQLiteStudio3/Tests/TestUtils/dbmanagermock.cpp new file mode 100644 index 0000000..1bc5f7d --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/dbmanagermock.cpp @@ -0,0 +1,81 @@ +#include "dbmanagermock.h" + +bool DbManagerMock::addDb(const QString&, const QString&, const QHash&, bool) +{ + return true; +} + +bool DbManagerMock::addDb(const QString&, const QString&, bool) +{ + return true; +} + +bool DbManagerMock::updateDb(Db*, const QString&, const QString&, const QHash&, bool) +{ + return true; +} + +void DbManagerMock::removeDbByName(const QString&, Qt::CaseSensitivity) +{ +} + +void DbManagerMock::removeDbByPath(const QString&) +{ +} + +void DbManagerMock::removeDb(Db*) +{ +} + +QList DbManagerMock::getDbList() +{ + return QList(); +} + +QList DbManagerMock::getValidDbList() +{ + return QList(); +} + +QList DbManagerMock::getConnectedDbList() +{ + return QList(); +} + +QStringList DbManagerMock::getDbNames() +{ + return QStringList(); +} + +Db* DbManagerMock::getByName(const QString&, Qt::CaseSensitivity) +{ + return nullptr; +} + +Db* DbManagerMock::getByPath(const QString&) +{ + return nullptr; +} + +Db*DbManagerMock::createInMemDb() +{ + return nullptr; +} + +bool DbManagerMock::isTemporary(Db*) +{ + return false; +} + +QString DbManagerMock::quickAddDb(const QString &, const QHash &) +{ + return QString(); +} + +void DbManagerMock::notifyDatabasesAreLoaded() +{ +} + +void DbManagerMock::scanForNewDatabasesInConfig() +{ +} diff --git a/SQLiteStudio3/Tests/TestUtils/dbmanagermock.h b/SQLiteStudio3/Tests/TestUtils/dbmanagermock.h new file mode 100644 index 0000000..2f28487 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/dbmanagermock.h @@ -0,0 +1,30 @@ +#ifndef DBMANAGERMOCK_H +#define DBMANAGERMOCK_H + +#include "services/dbmanager.h" + +class DbManagerMock : public DbManager +{ + public: + bool addDb(const QString& name, const QString&, const QHash&, bool); + bool addDb(const QString&, const QString&, bool); + bool updateDb(Db*, const QString&, const QString&, const QHash&, bool); + void removeDbByName(const QString&, Qt::CaseSensitivity); + void removeDbByPath(const QString&); + void removeDb(Db*); + QList getDbList(); + QList getValidDbList(); + QList getConnectedDbList(); + QStringList getDbNames(); + Db*getByName(const QString&, Qt::CaseSensitivity); + Db*getByPath(const QString&); + Db*createInMemDb(); + bool isTemporary(Db*); + QString quickAddDb(const QString &path, const QHash &); + + public slots: + void notifyDatabasesAreLoaded(); + void scanForNewDatabasesInConfig(); +}; + +#endif // DBMANAGERMOCK_H diff --git a/SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.cpp b/SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.cpp new file mode 100644 index 0000000..b5070e3 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.cpp @@ -0,0 +1,8 @@ +#include "dbsqlite3mock.h" +#include "common/unused.h" +#include + +DbSqlite3Mock::DbSqlite3Mock(const QString &name, const QString &path, const QHash &options) + : DbSqlite3(name, path, options) +{ +} diff --git a/SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.h b/SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.h new file mode 100644 index 0000000..52ca264 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/dbsqlite3mock.h @@ -0,0 +1,15 @@ +#ifndef DBSQLITE3MOCK_H +#define DBSQLITE3MOCK_H + +#include "db/dbsqlite3.h" + +class DbSqlite3Mock : public DbSqlite3 +{ + Q_OBJECT + + public: + DbSqlite3Mock(const QString& name, const QString& path = ":memory:", + const QHash &options = QHash()); +}; + +#endif // DBSQLITE3MOCK_H diff --git a/SQLiteStudio3/Tests/TestUtils/functionmanagermock.cpp b/SQLiteStudio3/Tests/TestUtils/functionmanagermock.cpp new file mode 100644 index 0000000..8657f42 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/functionmanagermock.cpp @@ -0,0 +1,38 @@ +#include "functionmanagermock.h" + +void FunctionManagerMock::setScriptFunctions(const QList &) +{ +} + +QList FunctionManagerMock::getAllScriptFunctions() const +{ + return QList(); +} + +QList FunctionManagerMock::getScriptFunctionsForDatabase(const QString&) const +{ + return QList(); +} + +QList FunctionManagerMock::getAllNativeFunctions() const +{ + return QList(); +} + +QVariant FunctionManagerMock::evaluateScalar(const QString&, int, const QList&, Db*, bool&) +{ + return QVariant(); +} + +void FunctionManagerMock::evaluateAggregateInitial(const QString&, int, Db*, QHash&) +{ +} + +void FunctionManagerMock::evaluateAggregateStep(const QString&, int, const QList&, Db*, QHash&) +{ +} + +QVariant FunctionManagerMock::evaluateAggregateFinal(const QString&, int, Db*, bool&, QHash&) +{ + return QVariant(); +} diff --git a/SQLiteStudio3/Tests/TestUtils/functionmanagermock.h b/SQLiteStudio3/Tests/TestUtils/functionmanagermock.h new file mode 100644 index 0000000..d7a9192 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/functionmanagermock.h @@ -0,0 +1,21 @@ +#ifndef FUNCTIONMANAGERMOCK_H +#define FUNCTIONMANAGERMOCK_H + +#include "services/functionmanager.h" + +#include + +class FunctionManagerMock : public FunctionManager +{ + public: + void setScriptFunctions(const QList&); + QList getAllScriptFunctions() const; + QList getScriptFunctionsForDatabase(const QString&) const; + QList getAllNativeFunctions() const; + QVariant evaluateScalar(const QString&, int, const QList&, Db*, bool&); + void evaluateAggregateInitial(const QString&, int, Db*, QHash&); + void evaluateAggregateStep(const QString&, int, const QList&, Db*, QHash&); + QVariant evaluateAggregateFinal(const QString&, int, Db*, bool&, QHash&); +}; + +#endif // FUNCTIONMANAGERMOCK_H diff --git a/SQLiteStudio3/Tests/TestUtils/hippomocks.h b/SQLiteStudio3/Tests/TestUtils/hippomocks.h new file mode 100644 index 0000000..671ea1a --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/hippomocks.h @@ -0,0 +1,2361 @@ +/* + * This is a Hippo Mocks, a mocking framework for C++. + * It's created by a HippoMocks team, see https://www.assembla.com/spaces/hippomocks/team + * + * Project page: https://www.assembla.com/spaces/hippomocks/wiki + * + * Licensed under LGPL. + */ + +#ifndef HIPPOMOCKS_H +#define HIPPOMOCKS_H + +#ifndef EXCEPTION_BUFFER_SIZE +#define EXCEPTION_BUFFER_SIZE 65536 +#endif + +#ifndef VIRT_FUNC_LIMIT +#define VIRT_FUNC_LIMIT 1024 +#elif VIRT_FUNC_LIMIT > 1024 +#error Adjust the code to support more than 1024 virtual functions before setting the VIRT_FUNC_LIMIT above 1024 +#endif + +#ifdef __EDG__ +#define FUNCTION_BASE 3 +#define FUNCTION_STRIDE 2 +#else +#define FUNCTION_BASE 0 +#define FUNCTION_STRIDE 1 +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +// these warnings are pointless and huge, and will confuse new users. +#pragma warning(push) +// If you can't generate an assignment operator the least you can do is shut up. +#pragma warning(disable: 4512) +// Alignment not right in a union? +#pragma warning(disable: 4121) +// Template parameters have the tendency not to change between executions. +#pragma warning(disable: 4127) +// No deprecated warnings on functions that really aren't deprecated at all. +#pragma warning(disable: 4996) +#endif +class MockRepository; + +enum RegistrationType { + Never, + Once, + DontCare +}; + +// base type +class base_mock { +public: + void destroy() { unwriteVft(); delete this; } + virtual ~base_mock() {} + void *rewriteVft(void *newVf) + { + void *oldVf = *(void **)this; + *(void **)this = newVf; + return oldVf; + } + void unwriteVft() + { + *(void **)this = (*(void ***)this)[-1]; + } +}; + +class NullType +{ +public: + bool operator==(const NullType &) const + { + return true; + } +}; + +struct NotPrintable { template NotPrintable(T const&) {} }; + +inline std::ostream &operator<<(std::ostream &os, NotPrintable const&) +{ + os << "???"; + return os; +} + +template +struct printArg +{ + static inline void print(std::ostream &os, T arg, bool withComma) + { + if (withComma) os << ","; + os << arg; + } +}; + +template <> +struct printArg +{ + static void print(std::ostream &, NullType , bool) + { + } +}; + +class base_tuple +{ +protected: + base_tuple() + { + } +public: + virtual ~base_tuple() + { + } + virtual bool operator==(const base_tuple &) const = 0; + virtual void printTo(std::ostream &os) const = 0; +}; + +template +struct no_cref { typedef X type; }; + +template +struct no_cref { typedef X type; }; + +struct NotComparable { template NotComparable(const T&) {} }; + +inline bool operator==(NotComparable, NotComparable) +{ + return false; +} + +template +struct comparer +{ + static inline bool compare(T a, T b) + { + return a == b; + } +}; + +template +class tuple : public base_tuple +{ +public: + typename no_cref::type a; + typename no_cref::type b; + typename no_cref::type c; + typename no_cref::type d; + typename no_cref::type e; + typename no_cref::type f; + typename no_cref::type g; + typename no_cref::type h; + typename no_cref::type i; + typename no_cref::type j; + typename no_cref::type k; + typename no_cref::type l; + typename no_cref::type m; + typename no_cref::type n; + typename no_cref::type o; + typename no_cref

::type p = typename no_cref

::type()) + : a(a), b(b), c(c), d(d), e(e), f(f), g(g), h(h), i(i), j(j), k(k), l(l), m(m), n(n), o(o), p(p) + {} + bool operator==(const base_tuple &bo) const { + const tuple &to = (const tuple &)bo; + return (comparer::compare(a, to.a) && + comparer::compare(b, to.b) && + comparer::compare(c, to.c) && + comparer::compare(d, to.d) && + comparer::compare(e, to.e) && + comparer::compare(f, to.f) && + comparer::compare(g, to.g) && + comparer::compare(h, to.h) && + comparer::compare(i, to.i) && + comparer::compare(j, to.j) && + comparer::compare(k, to.k) && + comparer::compare(l, to.l) && + comparer::compare(m, to.m) && + comparer::compare(n, to.n) && + comparer::compare(o, to.o) && + comparer

::compare(p, to.p)); + } + virtual void printTo(std::ostream &os) const + { + os << "("; + printArg::print(os, a, false); + printArg::print(os, b, true); + printArg::print(os, c, true); + printArg::print(os, d, true); + printArg::print(os, e, true); + printArg::print(os, f, true); + printArg::print(os, g, true); + printArg::print(os, h, true); + printArg::print(os, i, true); + printArg::print(os, j, true); + printArg::print(os, k, true); + printArg::print(os, l, true); + printArg::print(os, m, true); + printArg::print(os, n, true); + printArg::print(os, o, true); + printArg

::print(os, p, true); + os << ")"; + } +}; + +class BaseException : public std::exception { + char buffer[EXCEPTION_BUFFER_SIZE]; +public: + void setException(const char *description, MockRepository *repo); + const char *what() const throw() { return buffer; } +}; + +// exception types +class ExpectationException : public BaseException { +public: + ExpectationException(MockRepository *repo, const base_tuple *tuple, const char *funcName) + { + std::stringstream text; + text << "Function "; + text << funcName; + if (tuple) + tuple->printTo(text); + else + text << "(...)"; + text << " called with mismatching expectation!" << std::endl; + std::string description = text.str(); + setException(description.c_str(), repo); + } +}; + +class NotImplementedException : public BaseException { +public: + NotImplementedException(MockRepository *repo) + { + setException("Function called without expectation!", repo); + } +}; + +class CallMissingException : public BaseException { +public: + CallMissingException(MockRepository *repo) + { + setException("Function with expectation not called!", repo); + } +}; + +class NoResultSetUpException : public std::exception { + char buffer[EXCEPTION_BUFFER_SIZE]; +public: + const char *what() const throw() { return buffer; } + NoResultSetUpException(const base_tuple *tuple, const char *funcName) + { + std::stringstream text; + text << "No result set up on call to "; + text << funcName; + if (tuple) + tuple->printTo(text); + else + text << "(...)"; + text << std::endl; + std::string result = text.str(); + strncpy(buffer, result.c_str(), sizeof(buffer)-1); + } +}; + +// function-index-of-type +class func_index { +public: + virtual int f0() { return 0; } virtual int f1() { return 1; } virtual int f2() { return 2; } virtual int f3() { return 3; } + virtual int f4() { return 4; } virtual int f5() { return 5; } virtual int f6() { return 6; } virtual int f7() { return 7; } + virtual int f8() { return 8; } virtual int f9() { return 9; } virtual int f10() { return 10; } virtual int f11() { return 11; } + virtual int f12() { return 12; } virtual int f13() { return 13; } virtual int f14() { return 14; } virtual int f15() { return 15; } + virtual int f16() { return 16; } virtual int f17() { return 17; } virtual int f18() { return 18; } virtual int f19() { return 19; } + virtual int f20() { return 20; } virtual int f21() { return 21; } virtual int f22() { return 22; } virtual int f23() { return 23; } + virtual int f24() { return 24; } virtual int f25() { return 25; } virtual int f26() { return 26; } virtual int f27() { return 27; } + virtual int f28() { return 28; } virtual int f29() { return 29; } virtual int f30() { return 30; } virtual int f31() { return 31; } + virtual int f32() { return 32; } virtual int f33() { return 33; } virtual int f34() { return 34; } virtual int f35() { return 35; } + virtual int f36() { return 36; } virtual int f37() { return 37; } virtual int f38() { return 38; } virtual int f39() { return 39; } + virtual int f40() { return 40; } virtual int f41() { return 41; } virtual int f42() { return 42; } virtual int f43() { return 43; } + virtual int f44() { return 44; } virtual int f45() { return 45; } virtual int f46() { return 46; } virtual int f47() { return 47; } + virtual int f48() { return 48; } virtual int f49() { return 49; } virtual int f50() { return 50; } virtual int f51() { return 51; } + virtual int f52() { return 52; } virtual int f53() { return 53; } virtual int f54() { return 54; } virtual int f55() { return 55; } + virtual int f56() { return 56; } virtual int f57() { return 57; } virtual int f58() { return 58; } virtual int f59() { return 59; } + virtual int f60() { return 60; } virtual int f61() { return 61; } virtual int f62() { return 62; } virtual int f63() { return 63; } + virtual int f64() { return 64; } virtual int f65() { return 65; } virtual int f66() { return 66; } virtual int f67() { return 67; } + virtual int f68() { return 68; } virtual int f69() { return 69; } virtual int f70() { return 70; } virtual int f71() { return 71; } + virtual int f72() { return 72; } virtual int f73() { return 73; } virtual int f74() { return 74; } virtual int f75() { return 75; } + virtual int f76() { return 76; } virtual int f77() { return 77; } virtual int f78() { return 78; } virtual int f79() { return 79; } + virtual int f80() { return 80; } virtual int f81() { return 81; } virtual int f82() { return 82; } virtual int f83() { return 83; } + virtual int f84() { return 84; } virtual int f85() { return 85; } virtual int f86() { return 86; } virtual int f87() { return 87; } + virtual int f88() { return 88; } virtual int f89() { return 89; } virtual int f90() { return 90; } virtual int f91() { return 91; } + virtual int f92() { return 92; } virtual int f93() { return 93; } virtual int f94() { return 94; } virtual int f95() { return 95; } + virtual int f96() { return 96; } virtual int f97() { return 97; } virtual int f98() { return 98; } virtual int f99() { return 99; } + virtual int f100() { return 100; } virtual int f101() { return 101; } virtual int f102() { return 102; } virtual int f103() { return 103; } + virtual int f104() { return 104; } virtual int f105() { return 105; } virtual int f106() { return 106; } virtual int f107() { return 107; } + virtual int f108() { return 108; } virtual int f109() { return 109; } virtual int f110() { return 110; } virtual int f111() { return 111; } + virtual int f112() { return 112; } virtual int f113() { return 113; } virtual int f114() { return 114; } virtual int f115() { return 115; } + virtual int f116() { return 116; } virtual int f117() { return 117; } virtual int f118() { return 118; } virtual int f119() { return 119; } + virtual int f120() { return 120; } virtual int f121() { return 121; } virtual int f122() { return 122; } virtual int f123() { return 123; } + virtual int f124() { return 124; } virtual int f125() { return 125; } virtual int f126() { return 126; } virtual int f127() { return 127; } + virtual int f128() { return 128; } virtual int f129() { return 129; } virtual int f130() { return 130; } virtual int f131() { return 131; } + virtual int f132() { return 132; } virtual int f133() { return 133; } virtual int f134() { return 134; } virtual int f135() { return 135; } + virtual int f136() { return 136; } virtual int f137() { return 137; } virtual int f138() { return 138; } virtual int f139() { return 139; } + virtual int f140() { return 140; } virtual int f141() { return 141; } virtual int f142() { return 142; } virtual int f143() { return 143; } + virtual int f144() { return 144; } virtual int f145() { return 145; } virtual int f146() { return 146; } virtual int f147() { return 147; } + virtual int f148() { return 148; } virtual int f149() { return 149; } virtual int f150() { return 150; } virtual int f151() { return 151; } + virtual int f152() { return 152; } virtual int f153() { return 153; } virtual int f154() { return 154; } virtual int f155() { return 155; } + virtual int f156() { return 156; } virtual int f157() { return 157; } virtual int f158() { return 158; } virtual int f159() { return 159; } + virtual int f160() { return 160; } virtual int f161() { return 161; } virtual int f162() { return 162; } virtual int f163() { return 163; } + virtual int f164() { return 164; } virtual int f165() { return 165; } virtual int f166() { return 166; } virtual int f167() { return 167; } + virtual int f168() { return 168; } virtual int f169() { return 169; } virtual int f170() { return 170; } virtual int f171() { return 171; } + virtual int f172() { return 172; } virtual int f173() { return 173; } virtual int f174() { return 174; } virtual int f175() { return 175; } + virtual int f176() { return 176; } virtual int f177() { return 177; } virtual int f178() { return 178; } virtual int f179() { return 179; } + virtual int f180() { return 180; } virtual int f181() { return 181; } virtual int f182() { return 182; } virtual int f183() { return 183; } + virtual int f184() { return 184; } virtual int f185() { return 185; } virtual int f186() { return 186; } virtual int f187() { return 187; } + virtual int f188() { return 188; } virtual int f189() { return 189; } virtual int f190() { return 190; } virtual int f191() { return 191; } + virtual int f192() { return 192; } virtual int f193() { return 193; } virtual int f194() { return 194; } virtual int f195() { return 195; } + virtual int f196() { return 196; } virtual int f197() { return 197; } virtual int f198() { return 198; } virtual int f199() { return 199; } + virtual int f200() { return 200; } virtual int f201() { return 201; } virtual int f202() { return 202; } virtual int f203() { return 203; } + virtual int f204() { return 204; } virtual int f205() { return 205; } virtual int f206() { return 206; } virtual int f207() { return 207; } + virtual int f208() { return 208; } virtual int f209() { return 209; } virtual int f210() { return 210; } virtual int f211() { return 211; } + virtual int f212() { return 212; } virtual int f213() { return 213; } virtual int f214() { return 214; } virtual int f215() { return 215; } + virtual int f216() { return 216; } virtual int f217() { return 217; } virtual int f218() { return 218; } virtual int f219() { return 219; } + virtual int f220() { return 220; } virtual int f221() { return 221; } virtual int f222() { return 222; } virtual int f223() { return 223; } + virtual int f224() { return 224; } virtual int f225() { return 225; } virtual int f226() { return 226; } virtual int f227() { return 227; } + virtual int f228() { return 228; } virtual int f229() { return 229; } virtual int f230() { return 230; } virtual int f231() { return 231; } + virtual int f232() { return 232; } virtual int f233() { return 233; } virtual int f234() { return 234; } virtual int f235() { return 235; } + virtual int f236() { return 236; } virtual int f237() { return 237; } virtual int f238() { return 238; } virtual int f239() { return 239; } + virtual int f240() { return 240; } virtual int f241() { return 241; } virtual int f242() { return 242; } virtual int f243() { return 243; } + virtual int f244() { return 244; } virtual int f245() { return 245; } virtual int f246() { return 246; } virtual int f247() { return 247; } + virtual int f248() { return 248; } virtual int f249() { return 249; } virtual int f250() { return 250; } virtual int f251() { return 251; } + virtual int f252() { return 252; } virtual int f253() { return 253; } virtual int f254() { return 254; } virtual int f255() { return 255; } + virtual int f256() { return 256; } virtual int f257() { return 257; } virtual int f258() { return 258; } virtual int f259() { return 259; } + virtual int f260() { return 260; } virtual int f261() { return 261; } virtual int f262() { return 262; } virtual int f263() { return 263; } + virtual int f264() { return 264; } virtual int f265() { return 265; } virtual int f266() { return 266; } virtual int f267() { return 267; } + virtual int f268() { return 268; } virtual int f269() { return 269; } virtual int f270() { return 270; } virtual int f271() { return 271; } + virtual int f272() { return 272; } virtual int f273() { return 273; } virtual int f274() { return 274; } virtual int f275() { return 275; } + virtual int f276() { return 276; } virtual int f277() { return 277; } virtual int f278() { return 278; } virtual int f279() { return 279; } + virtual int f280() { return 280; } virtual int f281() { return 281; } virtual int f282() { return 282; } virtual int f283() { return 283; } + virtual int f284() { return 284; } virtual int f285() { return 285; } virtual int f286() { return 286; } virtual int f287() { return 287; } + virtual int f288() { return 288; } virtual int f289() { return 289; } virtual int f290() { return 290; } virtual int f291() { return 291; } + virtual int f292() { return 292; } virtual int f293() { return 293; } virtual int f294() { return 294; } virtual int f295() { return 295; } + virtual int f296() { return 296; } virtual int f297() { return 297; } virtual int f298() { return 298; } virtual int f299() { return 299; } + virtual int f300() { return 300; } virtual int f301() { return 301; } virtual int f302() { return 302; } virtual int f303() { return 303; } + virtual int f304() { return 304; } virtual int f305() { return 305; } virtual int f306() { return 306; } virtual int f307() { return 307; } + virtual int f308() { return 308; } virtual int f309() { return 309; } virtual int f310() { return 310; } virtual int f311() { return 311; } + virtual int f312() { return 312; } virtual int f313() { return 313; } virtual int f314() { return 314; } virtual int f315() { return 315; } + virtual int f316() { return 316; } virtual int f317() { return 317; } virtual int f318() { return 318; } virtual int f319() { return 319; } + virtual int f320() { return 320; } virtual int f321() { return 321; } virtual int f322() { return 322; } virtual int f323() { return 323; } + virtual int f324() { return 324; } virtual int f325() { return 325; } virtual int f326() { return 326; } virtual int f327() { return 327; } + virtual int f328() { return 328; } virtual int f329() { return 329; } virtual int f330() { return 330; } virtual int f331() { return 331; } + virtual int f332() { return 332; } virtual int f333() { return 333; } virtual int f334() { return 334; } virtual int f335() { return 335; } + virtual int f336() { return 336; } virtual int f337() { return 337; } virtual int f338() { return 338; } virtual int f339() { return 339; } + virtual int f340() { return 340; } virtual int f341() { return 341; } virtual int f342() { return 342; } virtual int f343() { return 343; } + virtual int f344() { return 344; } virtual int f345() { return 345; } virtual int f346() { return 346; } virtual int f347() { return 347; } + virtual int f348() { return 348; } virtual int f349() { return 349; } virtual int f350() { return 350; } virtual int f351() { return 351; } + virtual int f352() { return 352; } virtual int f353() { return 353; } virtual int f354() { return 354; } virtual int f355() { return 355; } + virtual int f356() { return 356; } virtual int f357() { return 357; } virtual int f358() { return 358; } virtual int f359() { return 359; } + virtual int f360() { return 360; } virtual int f361() { return 361; } virtual int f362() { return 362; } virtual int f363() { return 363; } + virtual int f364() { return 364; } virtual int f365() { return 365; } virtual int f366() { return 366; } virtual int f367() { return 367; } + virtual int f368() { return 368; } virtual int f369() { return 369; } virtual int f370() { return 370; } virtual int f371() { return 371; } + virtual int f372() { return 372; } virtual int f373() { return 373; } virtual int f374() { return 374; } virtual int f375() { return 375; } + virtual int f376() { return 376; } virtual int f377() { return 377; } virtual int f378() { return 378; } virtual int f379() { return 379; } + virtual int f380() { return 380; } virtual int f381() { return 381; } virtual int f382() { return 382; } virtual int f383() { return 383; } + virtual int f384() { return 384; } virtual int f385() { return 385; } virtual int f386() { return 386; } virtual int f387() { return 387; } + virtual int f388() { return 388; } virtual int f389() { return 389; } virtual int f390() { return 390; } virtual int f391() { return 391; } + virtual int f392() { return 392; } virtual int f393() { return 393; } virtual int f394() { return 394; } virtual int f395() { return 395; } + virtual int f396() { return 396; } virtual int f397() { return 397; } virtual int f398() { return 398; } virtual int f399() { return 399; } + virtual int f400() { return 400; } virtual int f401() { return 401; } virtual int f402() { return 402; } virtual int f403() { return 403; } + virtual int f404() { return 404; } virtual int f405() { return 405; } virtual int f406() { return 406; } virtual int f407() { return 407; } + virtual int f408() { return 408; } virtual int f409() { return 409; } virtual int f410() { return 410; } virtual int f411() { return 411; } + virtual int f412() { return 412; } virtual int f413() { return 413; } virtual int f414() { return 414; } virtual int f415() { return 415; } + virtual int f416() { return 416; } virtual int f417() { return 417; } virtual int f418() { return 418; } virtual int f419() { return 419; } + virtual int f420() { return 420; } virtual int f421() { return 421; } virtual int f422() { return 422; } virtual int f423() { return 423; } + virtual int f424() { return 424; } virtual int f425() { return 425; } virtual int f426() { return 426; } virtual int f427() { return 427; } + virtual int f428() { return 428; } virtual int f429() { return 429; } virtual int f430() { return 430; } virtual int f431() { return 431; } + virtual int f432() { return 432; } virtual int f433() { return 433; } virtual int f434() { return 434; } virtual int f435() { return 435; } + virtual int f436() { return 436; } virtual int f437() { return 437; } virtual int f438() { return 438; } virtual int f439() { return 439; } + virtual int f440() { return 440; } virtual int f441() { return 441; } virtual int f442() { return 442; } virtual int f443() { return 443; } + virtual int f444() { return 444; } virtual int f445() { return 445; } virtual int f446() { return 446; } virtual int f447() { return 447; } + virtual int f448() { return 448; } virtual int f449() { return 449; } virtual int f450() { return 450; } virtual int f451() { return 451; } + virtual int f452() { return 452; } virtual int f453() { return 453; } virtual int f454() { return 454; } virtual int f455() { return 455; } + virtual int f456() { return 456; } virtual int f457() { return 457; } virtual int f458() { return 458; } virtual int f459() { return 459; } + virtual int f460() { return 460; } virtual int f461() { return 461; } virtual int f462() { return 462; } virtual int f463() { return 463; } + virtual int f464() { return 464; } virtual int f465() { return 465; } virtual int f466() { return 466; } virtual int f467() { return 467; } + virtual int f468() { return 468; } virtual int f469() { return 469; } virtual int f470() { return 470; } virtual int f471() { return 471; } + virtual int f472() { return 472; } virtual int f473() { return 473; } virtual int f474() { return 474; } virtual int f475() { return 475; } + virtual int f476() { return 476; } virtual int f477() { return 477; } virtual int f478() { return 478; } virtual int f479() { return 479; } + virtual int f480() { return 480; } virtual int f481() { return 481; } virtual int f482() { return 482; } virtual int f483() { return 483; } + virtual int f484() { return 484; } virtual int f485() { return 485; } virtual int f486() { return 486; } virtual int f487() { return 487; } + virtual int f488() { return 488; } virtual int f489() { return 489; } virtual int f490() { return 490; } virtual int f491() { return 491; } + virtual int f492() { return 492; } virtual int f493() { return 493; } virtual int f494() { return 494; } virtual int f495() { return 495; } + virtual int f496() { return 496; } virtual int f497() { return 497; } virtual int f498() { return 498; } virtual int f499() { return 499; } + virtual int f500() { return 500; } virtual int f501() { return 501; } virtual int f502() { return 502; } virtual int f503() { return 503; } + virtual int f504() { return 504; } virtual int f505() { return 505; } virtual int f506() { return 506; } virtual int f507() { return 507; } + virtual int f508() { return 508; } virtual int f509() { return 509; } virtual int f510() { return 510; } virtual int f511() { return 511; } + virtual int f512() { return 512; } virtual int f513() { return 513; } virtual int f514() { return 514; } virtual int f515() { return 515; } + virtual int f516() { return 516; } virtual int f517() { return 517; } virtual int f518() { return 518; } virtual int f519() { return 519; } + virtual int f520() { return 520; } virtual int f521() { return 521; } virtual int f522() { return 522; } virtual int f523() { return 523; } + virtual int f524() { return 524; } virtual int f525() { return 525; } virtual int f526() { return 526; } virtual int f527() { return 527; } + virtual int f528() { return 528; } virtual int f529() { return 529; } virtual int f530() { return 530; } virtual int f531() { return 531; } + virtual int f532() { return 532; } virtual int f533() { return 533; } virtual int f534() { return 534; } virtual int f535() { return 535; } + virtual int f536() { return 536; } virtual int f537() { return 537; } virtual int f538() { return 538; } virtual int f539() { return 539; } + virtual int f540() { return 540; } virtual int f541() { return 541; } virtual int f542() { return 542; } virtual int f543() { return 543; } + virtual int f544() { return 544; } virtual int f545() { return 545; } virtual int f546() { return 546; } virtual int f547() { return 547; } + virtual int f548() { return 548; } virtual int f549() { return 549; } virtual int f550() { return 550; } virtual int f551() { return 551; } + virtual int f552() { return 552; } virtual int f553() { return 553; } virtual int f554() { return 554; } virtual int f555() { return 555; } + virtual int f556() { return 556; } virtual int f557() { return 557; } virtual int f558() { return 558; } virtual int f559() { return 559; } + virtual int f560() { return 560; } virtual int f561() { return 561; } virtual int f562() { return 562; } virtual int f563() { return 563; } + virtual int f564() { return 564; } virtual int f565() { return 565; } virtual int f566() { return 566; } virtual int f567() { return 567; } + virtual int f568() { return 568; } virtual int f569() { return 569; } virtual int f570() { return 570; } virtual int f571() { return 571; } + virtual int f572() { return 572; } virtual int f573() { return 573; } virtual int f574() { return 574; } virtual int f575() { return 575; } + virtual int f576() { return 576; } virtual int f577() { return 577; } virtual int f578() { return 578; } virtual int f579() { return 579; } + virtual int f580() { return 580; } virtual int f581() { return 581; } virtual int f582() { return 582; } virtual int f583() { return 583; } + virtual int f584() { return 584; } virtual int f585() { return 585; } virtual int f586() { return 586; } virtual int f587() { return 587; } + virtual int f588() { return 588; } virtual int f589() { return 589; } virtual int f590() { return 590; } virtual int f591() { return 591; } + virtual int f592() { return 592; } virtual int f593() { return 593; } virtual int f594() { return 594; } virtual int f595() { return 595; } + virtual int f596() { return 596; } virtual int f597() { return 597; } virtual int f598() { return 598; } virtual int f599() { return 599; } + virtual int f600() { return 600; } virtual int f601() { return 601; } virtual int f602() { return 602; } virtual int f603() { return 603; } + virtual int f604() { return 604; } virtual int f605() { return 605; } virtual int f606() { return 606; } virtual int f607() { return 607; } + virtual int f608() { return 608; } virtual int f609() { return 609; } virtual int f610() { return 610; } virtual int f611() { return 611; } + virtual int f612() { return 612; } virtual int f613() { return 613; } virtual int f614() { return 614; } virtual int f615() { return 615; } + virtual int f616() { return 616; } virtual int f617() { return 617; } virtual int f618() { return 618; } virtual int f619() { return 619; } + virtual int f620() { return 620; } virtual int f621() { return 621; } virtual int f622() { return 622; } virtual int f623() { return 623; } + virtual int f624() { return 624; } virtual int f625() { return 625; } virtual int f626() { return 626; } virtual int f627() { return 627; } + virtual int f628() { return 628; } virtual int f629() { return 629; } virtual int f630() { return 630; } virtual int f631() { return 631; } + virtual int f632() { return 632; } virtual int f633() { return 633; } virtual int f634() { return 634; } virtual int f635() { return 635; } + virtual int f636() { return 636; } virtual int f637() { return 637; } virtual int f638() { return 638; } virtual int f639() { return 639; } + virtual int f640() { return 640; } virtual int f641() { return 641; } virtual int f642() { return 642; } virtual int f643() { return 643; } + virtual int f644() { return 644; } virtual int f645() { return 645; } virtual int f646() { return 646; } virtual int f647() { return 647; } + virtual int f648() { return 648; } virtual int f649() { return 649; } virtual int f650() { return 650; } virtual int f651() { return 651; } + virtual int f652() { return 652; } virtual int f653() { return 653; } virtual int f654() { return 654; } virtual int f655() { return 655; } + virtual int f656() { return 656; } virtual int f657() { return 657; } virtual int f658() { return 658; } virtual int f659() { return 659; } + virtual int f660() { return 660; } virtual int f661() { return 661; } virtual int f662() { return 662; } virtual int f663() { return 663; } + virtual int f664() { return 664; } virtual int f665() { return 665; } virtual int f666() { return 666; } virtual int f667() { return 667; } + virtual int f668() { return 668; } virtual int f669() { return 669; } virtual int f670() { return 670; } virtual int f671() { return 671; } + virtual int f672() { return 672; } virtual int f673() { return 673; } virtual int f674() { return 674; } virtual int f675() { return 675; } + virtual int f676() { return 676; } virtual int f677() { return 677; } virtual int f678() { return 678; } virtual int f679() { return 679; } + virtual int f680() { return 680; } virtual int f681() { return 681; } virtual int f682() { return 682; } virtual int f683() { return 683; } + virtual int f684() { return 684; } virtual int f685() { return 685; } virtual int f686() { return 686; } virtual int f687() { return 687; } + virtual int f688() { return 688; } virtual int f689() { return 689; } virtual int f690() { return 690; } virtual int f691() { return 691; } + virtual int f692() { return 692; } virtual int f693() { return 693; } virtual int f694() { return 694; } virtual int f695() { return 695; } + virtual int f696() { return 696; } virtual int f697() { return 697; } virtual int f698() { return 698; } virtual int f699() { return 699; } + virtual int f700() { return 700; } virtual int f701() { return 701; } virtual int f702() { return 702; } virtual int f703() { return 703; } + virtual int f704() { return 704; } virtual int f705() { return 705; } virtual int f706() { return 706; } virtual int f707() { return 707; } + virtual int f708() { return 708; } virtual int f709() { return 709; } virtual int f710() { return 710; } virtual int f711() { return 711; } + virtual int f712() { return 712; } virtual int f713() { return 713; } virtual int f714() { return 714; } virtual int f715() { return 715; } + virtual int f716() { return 716; } virtual int f717() { return 717; } virtual int f718() { return 718; } virtual int f719() { return 719; } + virtual int f720() { return 720; } virtual int f721() { return 721; } virtual int f722() { return 722; } virtual int f723() { return 723; } + virtual int f724() { return 724; } virtual int f725() { return 725; } virtual int f726() { return 726; } virtual int f727() { return 727; } + virtual int f728() { return 728; } virtual int f729() { return 729; } virtual int f730() { return 730; } virtual int f731() { return 731; } + virtual int f732() { return 732; } virtual int f733() { return 733; } virtual int f734() { return 734; } virtual int f735() { return 735; } + virtual int f736() { return 736; } virtual int f737() { return 737; } virtual int f738() { return 738; } virtual int f739() { return 739; } + virtual int f740() { return 740; } virtual int f741() { return 741; } virtual int f742() { return 742; } virtual int f743() { return 743; } + virtual int f744() { return 744; } virtual int f745() { return 745; } virtual int f746() { return 746; } virtual int f747() { return 747; } + virtual int f748() { return 748; } virtual int f749() { return 749; } virtual int f750() { return 750; } virtual int f751() { return 751; } + virtual int f752() { return 752; } virtual int f753() { return 753; } virtual int f754() { return 754; } virtual int f755() { return 755; } + virtual int f756() { return 756; } virtual int f757() { return 757; } virtual int f758() { return 758; } virtual int f759() { return 759; } + virtual int f760() { return 760; } virtual int f761() { return 761; } virtual int f762() { return 762; } virtual int f763() { return 763; } + virtual int f764() { return 764; } virtual int f765() { return 765; } virtual int f766() { return 766; } virtual int f767() { return 767; } + virtual int f768() { return 768; } virtual int f769() { return 769; } virtual int f770() { return 770; } virtual int f771() { return 771; } + virtual int f772() { return 772; } virtual int f773() { return 773; } virtual int f774() { return 774; } virtual int f775() { return 775; } + virtual int f776() { return 776; } virtual int f777() { return 777; } virtual int f778() { return 778; } virtual int f779() { return 779; } + virtual int f780() { return 780; } virtual int f781() { return 781; } virtual int f782() { return 782; } virtual int f783() { return 783; } + virtual int f784() { return 784; } virtual int f785() { return 785; } virtual int f786() { return 786; } virtual int f787() { return 787; } + virtual int f788() { return 788; } virtual int f789() { return 789; } virtual int f790() { return 790; } virtual int f791() { return 791; } + virtual int f792() { return 792; } virtual int f793() { return 793; } virtual int f794() { return 794; } virtual int f795() { return 795; } + virtual int f796() { return 796; } virtual int f797() { return 797; } virtual int f798() { return 798; } virtual int f799() { return 799; } + virtual int f800() { return 800; } virtual int f801() { return 801; } virtual int f802() { return 802; } virtual int f803() { return 803; } + virtual int f804() { return 804; } virtual int f805() { return 805; } virtual int f806() { return 806; } virtual int f807() { return 807; } + virtual int f808() { return 808; } virtual int f809() { return 809; } virtual int f810() { return 810; } virtual int f811() { return 811; } + virtual int f812() { return 812; } virtual int f813() { return 813; } virtual int f814() { return 814; } virtual int f815() { return 815; } + virtual int f816() { return 816; } virtual int f817() { return 817; } virtual int f818() { return 818; } virtual int f819() { return 819; } + virtual int f820() { return 820; } virtual int f821() { return 821; } virtual int f822() { return 822; } virtual int f823() { return 823; } + virtual int f824() { return 824; } virtual int f825() { return 825; } virtual int f826() { return 826; } virtual int f827() { return 827; } + virtual int f828() { return 828; } virtual int f829() { return 829; } virtual int f830() { return 830; } virtual int f831() { return 831; } + virtual int f832() { return 832; } virtual int f833() { return 833; } virtual int f834() { return 834; } virtual int f835() { return 835; } + virtual int f836() { return 836; } virtual int f837() { return 837; } virtual int f838() { return 838; } virtual int f839() { return 839; } + virtual int f840() { return 840; } virtual int f841() { return 841; } virtual int f842() { return 842; } virtual int f843() { return 843; } + virtual int f844() { return 844; } virtual int f845() { return 845; } virtual int f846() { return 846; } virtual int f847() { return 847; } + virtual int f848() { return 848; } virtual int f849() { return 849; } virtual int f850() { return 850; } virtual int f851() { return 851; } + virtual int f852() { return 852; } virtual int f853() { return 853; } virtual int f854() { return 854; } virtual int f855() { return 855; } + virtual int f856() { return 856; } virtual int f857() { return 857; } virtual int f858() { return 858; } virtual int f859() { return 859; } + virtual int f860() { return 860; } virtual int f861() { return 861; } virtual int f862() { return 862; } virtual int f863() { return 863; } + virtual int f864() { return 864; } virtual int f865() { return 865; } virtual int f866() { return 866; } virtual int f867() { return 867; } + virtual int f868() { return 868; } virtual int f869() { return 869; } virtual int f870() { return 870; } virtual int f871() { return 871; } + virtual int f872() { return 872; } virtual int f873() { return 873; } virtual int f874() { return 874; } virtual int f875() { return 875; } + virtual int f876() { return 876; } virtual int f877() { return 877; } virtual int f878() { return 878; } virtual int f879() { return 879; } + virtual int f880() { return 880; } virtual int f881() { return 881; } virtual int f882() { return 882; } virtual int f883() { return 883; } + virtual int f884() { return 884; } virtual int f885() { return 885; } virtual int f886() { return 886; } virtual int f887() { return 887; } + virtual int f888() { return 888; } virtual int f889() { return 889; } virtual int f890() { return 890; } virtual int f891() { return 891; } + virtual int f892() { return 892; } virtual int f893() { return 893; } virtual int f894() { return 894; } virtual int f895() { return 895; } + virtual int f896() { return 896; } virtual int f897() { return 897; } virtual int f898() { return 898; } virtual int f899() { return 899; } + virtual int f900() { return 900; } virtual int f901() { return 901; } virtual int f902() { return 902; } virtual int f903() { return 903; } + virtual int f904() { return 904; } virtual int f905() { return 905; } virtual int f906() { return 906; } virtual int f907() { return 907; } + virtual int f908() { return 908; } virtual int f909() { return 909; } virtual int f910() { return 910; } virtual int f911() { return 911; } + virtual int f912() { return 912; } virtual int f913() { return 913; } virtual int f914() { return 914; } virtual int f915() { return 915; } + virtual int f916() { return 916; } virtual int f917() { return 917; } virtual int f918() { return 918; } virtual int f919() { return 919; } + virtual int f920() { return 920; } virtual int f921() { return 921; } virtual int f922() { return 922; } virtual int f923() { return 923; } + virtual int f924() { return 924; } virtual int f925() { return 925; } virtual int f926() { return 926; } virtual int f927() { return 927; } + virtual int f928() { return 928; } virtual int f929() { return 929; } virtual int f930() { return 930; } virtual int f931() { return 931; } + virtual int f932() { return 932; } virtual int f933() { return 933; } virtual int f934() { return 934; } virtual int f935() { return 935; } + virtual int f936() { return 936; } virtual int f937() { return 937; } virtual int f938() { return 938; } virtual int f939() { return 939; } + virtual int f940() { return 940; } virtual int f941() { return 941; } virtual int f942() { return 942; } virtual int f943() { return 943; } + virtual int f944() { return 944; } virtual int f945() { return 945; } virtual int f946() { return 946; } virtual int f947() { return 947; } + virtual int f948() { return 948; } virtual int f949() { return 949; } virtual int f950() { return 950; } virtual int f951() { return 951; } + virtual int f952() { return 952; } virtual int f953() { return 953; } virtual int f954() { return 954; } virtual int f955() { return 955; } + virtual int f956() { return 956; } virtual int f957() { return 957; } virtual int f958() { return 958; } virtual int f959() { return 959; } + virtual int f960() { return 960; } virtual int f961() { return 961; } virtual int f962() { return 962; } virtual int f963() { return 963; } + virtual int f964() { return 964; } virtual int f965() { return 965; } virtual int f966() { return 966; } virtual int f967() { return 967; } + virtual int f968() { return 968; } virtual int f969() { return 969; } virtual int f970() { return 970; } virtual int f971() { return 971; } + virtual int f972() { return 972; } virtual int f973() { return 973; } virtual int f974() { return 974; } virtual int f975() { return 975; } + virtual int f976() { return 976; } virtual int f977() { return 977; } virtual int f978() { return 978; } virtual int f979() { return 979; } + virtual int f980() { return 980; } virtual int f981() { return 981; } virtual int f982() { return 982; } virtual int f983() { return 983; } + virtual int f984() { return 984; } virtual int f985() { return 985; } virtual int f986() { return 986; } virtual int f987() { return 987; } + virtual int f988() { return 988; } virtual int f989() { return 989; } virtual int f990() { return 990; } virtual int f991() { return 991; } + virtual int f992() { return 992; } virtual int f993() { return 993; } virtual int f994() { return 994; } virtual int f995() { return 995; } + virtual int f996() { return 996; } virtual int f997() { return 997; } virtual int f998() { return 998; } virtual int f999() { return 999; } + virtual int f1000() { return 1000; } virtual int f1001() { return 1001; } virtual int f1002() { return 1002; } virtual int f1003() { return 1003; } + virtual int f1004() { return 1004; } virtual int f1005() { return 1005; } virtual int f1006() { return 1006; } virtual int f1007() { return 1007; } + virtual int f1008() { return 1008; } virtual int f1009() { return 1009; } virtual int f1010() { return 1010; } virtual int f1011() { return 1011; } + virtual int f1012() { return 1012; } virtual int f1013() { return 1013; } virtual int f1014() { return 1014; } virtual int f1015() { return 1015; } + virtual int f1016() { return 1016; } virtual int f1017() { return 1017; } virtual int f1018() { return 1018; } virtual int f1019() { return 1019; } + virtual int f1020() { return 1020; } virtual int f1021() { return 1021; } virtual int f1022() { return 1022; } virtual int f1023() { return 1023; } +}; + +template +T getNonvirtualMemberFunctionAddress(U u) +{ +#ifdef __EDG__ + // Edison Design Group C++ frontend (Comeau, Portland Group, Greenhills, etc) + union { + struct { + short delta; + short vindex; + T t; + } mfp_structure; + U u; + } conv; +#else + // Visual Studio, GCC, others + union { + struct { + T t; + } mfp_structure; + U u; + } conv; +#endif + conv.u = u; + return conv.mfp_structure.t; +} + +template +int getFunctionIndex(T func) { + func_index idx; + return ((&idx)->*reinterpret_cast(func))() * FUNCTION_STRIDE + FUNCTION_BASE; +} + +// mock types +template +class mock : public base_mock +{ + friend class MockRepository; + unsigned char remaining[sizeof(T)]; + void NotImplemented() { throw NotImplementedException(repo); } +protected: + void *oldVft; + void (*funcs[VIRT_FUNC_LIMIT])(); + MockRepository *repo; +public: + int funcMap[VIRT_FUNC_LIMIT]; + mock(MockRepository *repo) + : repo(repo) + { + for (int i = 0; i < VIRT_FUNC_LIMIT; i++) + { + funcs[i] = getNonvirtualMemberFunctionAddress(&mock::NotImplemented); + funcMap[i] = -1; + } + memset(remaining, 0, sizeof(remaining)); + oldVft = base_mock::rewriteVft(funcs); + } + int translateX(int x) + { + for (int i = 0; i < VIRT_FUNC_LIMIT; i++) + { + if (funcMap[i] == x) return i; + } + return -1; + } +}; + +template +class classMock : public mock +{ + void *backupVft; +public: + classMock(MockRepository *repo) + : mock(repo) + { + mock::oldVft = base_mock::rewriteVft((void *)mock::funcs); + new(this)T(); + backupVft = base_mock::rewriteVft((void *)mock::funcs); + } + ~classMock() + { + base_mock::rewriteVft(backupVft); + ((T *)this)->~T(); + } +}; + +//Type-safe exception wrapping +class ExceptionHolder +{ +public: + virtual ~ExceptionHolder() {} + virtual void rethrow() = 0; +}; + +template +class ExceptionWrapper : public ExceptionHolder { + T exception; +public: + ExceptionWrapper(T exception) : exception(exception) {} + void rethrow() { throw exception; } +}; + +// Do() function wrapping +class VirtualDestructable { public: virtual ~VirtualDestructable() {} }; + +template +class TupleInvocable : public VirtualDestructable +{ +public: + virtual Y operator()(const base_tuple &tupl) = 0; +}; + +template +class Invocable : public TupleInvocable +{ +public: + virtual Y operator()(A a = A(), B b = B(), C c = C(), D d = D(), E e = E(), F f = F(), G g = G(), H h = H(), I i = I(), J j = J(), K k = K(), L l = L(), M m = M(), N n = N(), O o = O(), P p = P()) = 0; + virtual Y operator()(const base_tuple &tupl) { + const tuple &rTupl = reinterpret_cast &>(tupl); + return (*this)(rTupl.a, rTupl.b, rTupl.c, rTupl.d, rTupl.e, rTupl.f, rTupl.g, rTupl.h, + rTupl.i, rTupl.j, rTupl.k, rTupl.l, rTupl.m, rTupl.n, rTupl.o, rTupl.p); + } +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n, O o, P p) + { + return t(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n, O o, NullType) + { + return t(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n, NullType, NullType) + { + return t(a,b,c,d,e,f,g,h,i,j,k,l,m,n); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, NullType, NullType, NullType) + { + return t(a,b,c,d,e,f,g,h,i,j,k,l,m); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d,e,f,g,h,i,j,k,l); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d,e,f,g,h,i,j,k); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d,e,f,g,h,i,j); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable{ + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, I i, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d,e,f,g,h,i); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, H h, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d,e,f,g,h); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, G g, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d,e,f,g); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, F f, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d,e,f); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, E e, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d,e); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, D d, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c,d); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, C c, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b,c); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, B b, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a,b); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(A a, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(a); + } + using Invocable::operator(); +}; +template +class DoWrapper : public Invocable { + T &t; +public: + DoWrapper(T &t) : t(t) {} + virtual Y operator()(NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType, NullType) + { + return t(); + } + using Invocable::operator(); +}; + +class ReturnValueHolder { +public: + virtual ~ReturnValueHolder() {} +}; + +template +class ReturnValueWrapper : public ReturnValueHolder { +public: + typename no_cref::type rv; + ReturnValueWrapper(T rv) : rv(rv) {} +}; + +//Call wrapping +class Call { +public: + virtual bool matchesArgs(const base_tuple &tuple) = 0; + ReturnValueHolder *retVal; + ExceptionHolder *eHolder; + base_mock *mock; + VirtualDestructable *functor; + int funcIndex; + std::list previousCalls; + RegistrationType expectation; + bool satisfied; + int lineno; + const char *funcName; + const char *fileName; +protected: + Call(RegistrationType expectation, base_mock *mock, int funcIndex, int X, const char *funcName, const char *fileName) + : retVal(0), + eHolder(0), + mock(mock), + functor(0), + funcIndex(funcIndex), + expectation(expectation), + satisfied(false), + lineno(X), + funcName(funcName), + fileName(fileName) + { + } +public: + virtual const base_tuple *getArgs() const = 0; + virtual ~Call() + { + delete eHolder; + delete functor; + delete retVal; + } +}; + +std::ostream &operator<<(std::ostream &os, const Call &call); + +template +class TCall : public Call { +private: + tuple *args; +public: + const base_tuple *getArgs() const { return args; } + TCall(RegistrationType expectation, base_mock *mock, int funcIndex, int X, const char *funcName, const char *fileName) : Call(expectation, mock, funcIndex, X, funcName, fileName), args(0) {} + ~TCall() { delete args; } + bool matchesArgs(const base_tuple &tupl) { return !args || *args == reinterpret_cast &>(tupl); } + TCall &With(A a = A(), B b = B(), C c = C(), D d = D(), E e = E(), F f = F(), G g = G(), H h = H(), I i = I(), J j = J(), K k = K(), L l = L(), M m = M(), N n = N(), O o = O(), P p = P()) { + args = new tuple(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p); + return *this; + } + TCall &After(Call &call) { + previousCalls.push_back(&call); + return *this; + } + template + Call &Do(T &function) { functor = new DoWrapper(function); return *this; } + Call &Return(Y obj) { retVal = new ReturnValueWrapper(obj); return *this; } + template + Call &Throw(Ex exception) { eHolder = new ExceptionWrapper(exception); return *this; } +}; + +template +class TCall : public Call { +private: + tuple *args; +public: + const base_tuple *getArgs() const { return args; } + TCall(RegistrationType expectation, base_mock *mock, int funcIndex, int X, const char *funcName, const char *fileName) : Call(expectation, mock, funcIndex, X, funcName, fileName), args(0) {} + ~TCall() { delete args; } + bool matchesArgs(const base_tuple &tupl) { return (!args) || (*args == reinterpret_cast &>(tupl)); } + TCall &With(A a = A(), B b = B(), C c = C(), D d = D(), E e = E(), F f = F(), G g = G(), H h = H(), I i = I(), J j = J(), K k = K(), L l = L(), M m = M(), N n = N(), O o = O(), P p = P()) { + args = new tuple(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p); + return *this; + } + TCall &After(Call &call) { + previousCalls.push_back(&call); + return *this; + } + template + Call &Do(T &function) { functor = new DoWrapper(function); return *this; } + template + Call &Throw(Ex exception) { eHolder = new ExceptionWrapper(exception); return *this; } +}; + +class MockRepository { +private: + friend inline std::ostream &operator<<(std::ostream &os, const MockRepository &repo); + std::list mocks; + std::list neverCalls; + std::list expectations; + std::list optionals; +public: + bool autoExpect; +#ifdef _MSC_VER +#define OnCall(obj, func) RegisterExpect_<__COUNTER__, DontCare>(obj, &func, #func, __FILE__, __LINE__) +#define ExpectCall(obj, func) RegisterExpect_<__COUNTER__, Once>(obj, &func, #func, __FILE__, __LINE__) +#define NeverCall(obj, func) RegisterExpect_<__COUNTER__, Never>(obj, &func, #func, __FILE__, __LINE__) +#define OnCallOverload(obj, func) RegisterExpect_<__COUNTER__, DontCare>(obj, func, #func, __FILE__, __LINE__) +#define ExpectCallOverload(obj, func) RegisterExpect_<__COUNTER__, Once>(obj, func, #func, __FILE__, __LINE__) +#define NeverCallOverload(obj, func) RegisterExpect_<__COUNTER__, Never>(obj, func, #func, __FILE__, __LINE__) +#else +#define OnCall(obj, func) RegisterExpect_<__LINE__, DontCare>(obj, &func, #func, __FILE__, __LINE__) +#define ExpectCall(obj, func) RegisterExpect_<__LINE__, Once>(obj, &func, #func, __FILE__, __LINE__) +#define NeverCall(obj, func) RegisterExpect_<__LINE__, Never>(obj, &func, #func, __FILE__, __LINE__) +#define OnCallOverload(obj, func) RegisterExpect_<__LINE__, DontCare>(obj, func, #func, __FILE__, __LINE__) +#define ExpectCallOverload(obj, func) RegisterExpect_<__LINE__, Once>(obj, func, #func, __FILE__, __LINE__) +#define NeverCallOverload(obj, func) RegisterExpect_<__LINE__, Never>(obj, func, #func, __FILE__, __LINE__) +#endif + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O), const char *funcName, const char *fileName, unsigned long lineNo); + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P), const char *funcName, const char *fileName, unsigned long lineNo); + + //GCC 3.x doesn't seem to understand overloading on const or non-const member function. Volatile appears to work though. +#if !defined(__GNUC__) || __GNUC__ > 3 + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)() volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)())(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P))(func), funcName, fileName, lineNo); } + + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)() const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)())(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) const volatile, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P))(func), funcName, fileName, lineNo); } + + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)() const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)())(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O))(func), funcName, fileName, lineNo); } + template + TCall &RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) const, const char *funcName, const char *fileName, unsigned long lineNo) { return RegisterExpect_(mck, (Y(Z::*)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P))(func), funcName, fileName, lineNo); } +#endif + template + void BasicRegisterExpect(mock *zMock, int funcIndex, void (base_mock::*func)(), int X); + template + Z DoExpectation(base_mock *mock, int funcno, const base_tuple &tuple); + void DoVoidExpectation(base_mock *mock, int funcno, const base_tuple &tuple) + { + for (std::list::iterator i = expectations.begin(); i != expectations.end(); ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno && + call->matchesArgs(tuple) && + !call->satisfied) + { + bool allSatisfy = true; + for (std::list::iterator callsBefore = call->previousCalls.begin(); + callsBefore != call->previousCalls.end(); ++callsBefore) + { + if (!(*callsBefore)->satisfied) + { + allSatisfy = false; + } + } + if (!allSatisfy) continue; + + call->satisfied = true; + + if (call->eHolder) + call->eHolder->rethrow(); + + if (call->functor != NULL) + (*(TupleInvocable *)(call->functor))(tuple); + + return; + } + } + for (std::list::iterator i = neverCalls.begin(); i != neverCalls.end(); ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno && + call->matchesArgs(tuple)) + { + bool allSatisfy = true; + for (std::list::iterator callsBefore = call->previousCalls.begin(); + callsBefore != call->previousCalls.end(); ++callsBefore) + { + if (!(*callsBefore)->satisfied) + { + allSatisfy = false; + } + } + if (!allSatisfy) continue; + + call->satisfied = true; + + throw ExpectationException(this, call->getArgs(), call->funcName); + } + } + for (std::list::iterator i = optionals.begin(); i != optionals.end(); ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno && + call->matchesArgs(tuple)) + { + bool allSatisfy = true; + for (std::list::iterator callsBefore = call->previousCalls.begin(); + callsBefore != call->previousCalls.end(); ++callsBefore) + { + if (!(*callsBefore)->satisfied) + { + allSatisfy = false; + } + } + if (!allSatisfy) continue; + + call->satisfied = true; + + if (call->eHolder) + call->eHolder->rethrow(); + + if (call->functor != NULL) + (*(TupleInvocable *)(call->functor))(tuple); + + return; + } + } + const char *funcName = NULL; + for (std::list::iterator i = expectations.begin(); i != expectations.end() && !funcName; ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno) + funcName = call->funcName; + } + for (std::list::iterator i = optionals.begin(); i != optionals.end() && !funcName; ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno) + funcName = call->funcName; + } + for (std::list::iterator i = neverCalls.begin(); i != neverCalls.end() && !funcName; ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno) + funcName = call->funcName; + } + throw ExpectationException(this, &tuple, funcName); + } + MockRepository() + : autoExpect(true) + { + } + ~MockRepository() + { + if (!std::uncaught_exception()) + VerifyAll(); + reset(); + for (std::list::iterator i = mocks.begin(); i != mocks.end(); i++) + { + (*i)->destroy(); + } + mocks.clear(); + } + void reset() + { + for (std::list::iterator i = expectations.begin(); i != expectations.end(); i++) + { + delete *i; + } + expectations.clear(); + for (std::list::iterator i = neverCalls.begin(); i != neverCalls.end(); i++) + { + delete *i; + } + neverCalls.clear(); + for (std::list::iterator i = optionals.begin(); i != optionals.end(); i++) + { + delete *i; + } + optionals.clear(); + } + void VerifyAll() + { + for (std::list::iterator i = expectations.begin(); i != expectations.end(); i++) + { + if (!(*i)->satisfied) + throw CallMissingException(this); + } + } + template + base *InterfaceMock(); + template + base *ClassMock(); +}; + +// mock function providers +template +class mockFuncs : public mock { +private: + mockFuncs(); +public: + template + Y expectation0() + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple<>()); + } + template + Y expectation1(A a) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a)); + } + template + Y expectation2(A a, B b) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b)); + } + template + Y expectation3(A a, B b, C c) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c)); + } + template + Y expectation4(A a, B b, C c, D d) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d)); + } + template + Y expectation5(A a, B b, C c, D d, E e) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e)); + } + template + Y expectation6(A a, B b, C c, D d, E e, F f) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f)); + } + template + Y expectation7(A a, B b, C c, D d, E e, F f, G g) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g)); + } + template + Y expectation8(A a, B b, C c, D d, E e, F f, G g, H h) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), new tuple(a,b,c,d,e,f,g,h)); + } + template + Y expectation9(A a, B b, C c, D d, E e, F f, G g, H h, I i) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i)); + } + template + Y expectation10(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j)); + } + template + Y expectation11(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k)); + } + template + Y expectation12(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l)); + } + template + Y expectation13(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l,m)); + } + template + Y expectation14(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l,m,n)); + } + template + Y expectation15(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n, O o) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)); + } + template + Y expectation16(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n, O o, P p) + { + MockRepository *repo = mock::repo; + return repo->template DoExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)); + } +}; + +template +class mockFuncs : public mock { +private: + mockFuncs(); +public: + template + void expectation0() + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple<>()); + } + template + void expectation1(A a) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a)); + } + template + void expectation2(A a, B b) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b)); + } + template + void expectation3(A a, B b, C c) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c)); + } + template + void expectation4(A a, B b, C c, D d) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d)); + } + template + void expectation5(A a, B b, C c, D d, E e) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e)); + } + template + void expectation6(A a, B b, C c, D d, E e, F f) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f)); + } + template + void expectation7(A a, B b, C c, D d, E e, F f, G g) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g)); + } + template + void expectation8(A a, B b, C c, D d, E e, F f, G g, H h) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h)); + } + template + void expectation9(A a, B b, C c, D d, E e, F f, G g, H h, I i) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i)); + } + template + void expectation10(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j)); + } + template + void expectation11(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k)); + } + template + void expectation12(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l)); + } + template + void expectation13(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l,m)); + } + template + void expectation14(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l,m,n)); + } + template + void expectation15(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n, O o) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)); + } + template + void expectation16(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n, O o, P p) + { + MockRepository *repo = mock::repo; + repo->DoVoidExpectation(this, mock::translateX(X), tuple(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)); + } +}; + +template +void MockRepository::BasicRegisterExpect(mock *zMock, int funcIndex, void (base_mock::*func)(), int X) +{ + if (zMock->funcMap[funcIndex] == -1) + { + zMock->funcs[funcIndex] = getNonvirtualMemberFunctionAddress(func); + zMock->funcMap[funcIndex] = X; + } +} + +// Mock repository implementation +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(); + mfp = &mockFuncs::template expectation0; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A); + mfp = &mockFuncs::template expectation1; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B); + mfp = &mockFuncs::template expectation2; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C); + mfp = &mockFuncs::template expectation3; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D); + mfp = &mockFuncs::template expectation4; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E); + mfp = &mockFuncs::template expectation5; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F); + mfp = &mockFuncs::template expectation6; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G); + mfp = &mockFuncs::template expectation7; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H); + mfp = &mockFuncs::template expectation8; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H,I); + mfp = &mockFuncs::template expectation9; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H,I,J); + mfp = &mockFuncs::template expectation10; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H,I,J,K); + mfp = &mockFuncs::template expectation11; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H,I,J,K,L); + mfp = &mockFuncs::template expectation12; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H,I,J,K,L,M); + mfp = &mockFuncs::template expectation13; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H,I,J,K,L,M,N); + mfp = &mockFuncs::template expectation14; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O); + mfp = &mockFuncs::template expectation15; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} + +template +TCall &MockRepository::RegisterExpect_(Z2 *mck, Y (Z::*func)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P), const char *funcName, const char *fileName, unsigned long lineNo) +{ + int funcIndex = getFunctionIndex(func); + Y (mockFuncs::*mfp)(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P); + mfp = &mockFuncs::template expectation16; + BasicRegisterExpect(reinterpret_cast *>(mck), + funcIndex, + reinterpret_cast(mfp),X); + TCall *call = new TCall(expect, reinterpret_cast(mck), funcIndex, lineNo, funcName, fileName); + switch(expect) + { + case Never: neverCalls.push_back(call); break; + case DontCare: optionals.push_back(call); break; + case Once: + if (autoExpect && expectations.size() > 0) + { + call->previousCalls.push_back(expectations.back()); + } + expectations.push_back(call); + break; + } + return *call; +} + +template +Z MockRepository::DoExpectation(base_mock *mock, int funcno, const base_tuple &tuple) +{ + for (std::list::iterator i = expectations.begin(); i != expectations.end(); ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno && + call->matchesArgs(tuple) && + !call->satisfied) + { + bool allSatisfy = true; + for (std::list::iterator callsBefore = call->previousCalls.begin(); + callsBefore != call->previousCalls.end(); ++callsBefore) + { + if (!(*callsBefore)->satisfied) + { + allSatisfy = false; + } + } + if (!allSatisfy) continue; + + call->satisfied = true; + + if (call->eHolder) + call->eHolder->rethrow(); + + if (call->retVal) + return ((ReturnValueWrapper *)call->retVal)->rv; + + if (call->functor != NULL) + return (*(TupleInvocable *)(call->functor))(tuple); + + throw NoResultSetUpException(call->getArgs(), call->funcName); + } + } + for (std::list::iterator i = neverCalls.begin(); i != neverCalls.end(); ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno && + call->matchesArgs(tuple)) + { + bool allSatisfy = true; + for (std::list::iterator callsBefore = call->previousCalls.begin(); + callsBefore != call->previousCalls.end(); ++callsBefore) + { + if (!(*callsBefore)->satisfied) + { + allSatisfy = false; + } + } + if (!allSatisfy) continue; + + call->satisfied = true; + + throw ExpectationException(this, call->getArgs(), call->funcName); + } + } + for (std::list::iterator i = optionals.begin(); i != optionals.end(); ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno && + call->matchesArgs(tuple)) + { + bool allSatisfy = true; + for (std::list::iterator callsBefore = call->previousCalls.begin(); + callsBefore != call->previousCalls.end(); ++callsBefore) + { + if (!(*callsBefore)->satisfied) + { + allSatisfy = false; + } + } + if (!allSatisfy) continue; + + call->satisfied = true; + + if (call->eHolder) + call->eHolder->rethrow(); + + if (call->retVal) + return ((ReturnValueWrapper *)call->retVal)->rv; + + if (call->functor != NULL) + return (*(TupleInvocable *)(call->functor))(tuple); + + throw NoResultSetUpException(call->getArgs(), call->funcName); + } + } + const char *funcName = NULL; + for (std::list::iterator i = expectations.begin(); i != expectations.end() && !funcName; ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno) + funcName = call->funcName; + } + for (std::list::iterator i = neverCalls.begin(); i != neverCalls.end() && !funcName; ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno) + funcName = call->funcName; + } + for (std::list::iterator i = optionals.begin(); i != optionals.end() && !funcName; ++i) + { + Call *call = *i; + if (call->mock == mock && + call->funcIndex == funcno) + funcName = call->funcName; + } + throw ExpectationException(this, &tuple, funcName); +} +template +base *MockRepository::InterfaceMock() { + mock *m = new mock(this); + mocks.push_back(m); + return reinterpret_cast(m); +} +template +base *MockRepository::ClassMock() { + classMock *m = new classMock(this); + mocks.push_back(m); + return reinterpret_cast(m); +} + +inline std::ostream &operator<<(std::ostream &os, const Call &call) +{ + os << call.fileName << "(" << call.lineno << ") "; + if (call.expectation == Once) + os << "Expectation for "; + else + os << "Result set for "; + + os << call.funcName; + + if (call.getArgs()) + call.getArgs()->printTo(os); + else + os << "(...)"; + + os << " on the mock at 0x" << call.mock << " was "; + + if (!call.satisfied) + os << "not "; + + if (call.expectation == Once) + os << "satisfied." << std::endl; + else + os << "used." << std::endl; + + return os; +} + +inline std::ostream &operator<<(std::ostream &os, const MockRepository &repo) +{ + if (repo.expectations.size()) + { + os << "Expections set:" << std::endl; + for (std::list::const_iterator exp = repo.expectations.begin(); exp != repo.expectations.end(); ++exp) + os << **exp; + os << std::endl; + } + + if (repo.neverCalls.size()) + { + os << "Functions explicitly expected to not be called:" << std::endl; + for (std::list::const_iterator exp = repo.neverCalls.begin(); exp != repo.neverCalls.end(); ++exp) + os << **exp; + os << std::endl; + } + + if (repo.optionals.size()) + { + os << "Optional results set up:" << std::endl; + for (std::list::const_iterator exp = repo.optionals.begin(); exp != repo.optionals.end(); ++exp) + os << **exp; + os << std::endl; + } + + return os; +} + +inline void BaseException::setException(const char *description, MockRepository *repo) +{ + std::stringstream text; + text << description; + text << *repo; + std::string result = text.str(); + strncpy(buffer, result.c_str(), sizeof(buffer)-1); + buffer[sizeof(buffer)-1] = '\0'; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/SQLiteStudio3/Tests/TestUtils/mocks.cpp b/SQLiteStudio3/Tests/TestUtils/mocks.cpp new file mode 100644 index 0000000..dee3bfd --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/mocks.cpp @@ -0,0 +1,37 @@ +#include "mocks.h" +#include "common/global.h" +#include "sqlitestudio.h" +#include "configmock.h" +#include "pluginmanagermock.h" +#include "functionmanagermock.h" +#include "collationmanagermock.h" +#include "dbattachermock.h" +#include "dbmanagermock.h" + +MockRepository* mockRepository = nullptr; + +MockRepository& mockRepo() +{ + if (!mockRepository) + { + mockRepository = new MockRepository; + mockRepository->autoExpect = false; + } + + return *mockRepository; +} + +void deleteMockRepo() +{ + safe_delete(mockRepository); +} + +void initMocks() +{ + SQLITESTUDIO->setConfig(new ConfigMock()); + SQLITESTUDIO->setFunctionManager(new FunctionManagerMock()); + SQLITESTUDIO->setPluginManager(new PluginManagerMock()); + SQLITESTUDIO->setDbAttacherFactory(new DbAttacherFactoryMock()); + SQLITESTUDIO->setDbManager(new DbManagerMock()); + SQLITESTUDIO->setCollationManager(new CollationManagerMock()); +} diff --git a/SQLiteStudio3/Tests/TestUtils/mocks.h b/SQLiteStudio3/Tests/TestUtils/mocks.h new file mode 100644 index 0000000..f3bd1f9 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/mocks.h @@ -0,0 +1,11 @@ +#ifndef UNITS_SETUP_H +#define UNITS_SETUP_H + +#include "hippomocks.h" + +MockRepository& mockRepo(); +void deleteMockRepo(); + +void initMocks(); + +#endif // UNITS_SETUP_H diff --git a/SQLiteStudio3/Tests/TestUtils/pluginmanagermock.cpp b/SQLiteStudio3/Tests/TestUtils/pluginmanagermock.cpp new file mode 100644 index 0000000..a8f9bca --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/pluginmanagermock.cpp @@ -0,0 +1,162 @@ +#include "pluginmanagermock.h" + +void PluginManagerMock::init() +{ +} + +void PluginManagerMock::deinit() +{ +} + +QList PluginManagerMock::getPluginTypes() const +{ + return QList(); +} + +QStringList PluginManagerMock::getPluginDirs() const +{ + return QStringList(); +} + +QString PluginManagerMock::getFilePath(Plugin*) const +{ + return QString(); +} + +bool PluginManagerMock::loadBuiltInPlugin(Plugin*) +{ + return true; +} + +bool PluginManagerMock::load(const QString&) +{ + return true; +} + +void PluginManagerMock::unload(const QString&) +{ +} + +void PluginManagerMock::unload(Plugin*) +{ +} + +bool PluginManagerMock::isLoaded(const QString&) const +{ + return false; +} + +bool PluginManagerMock::isBuiltIn(const QString&) const +{ + return false; +} + +Plugin* PluginManagerMock::getLoadedPlugin(const QString&) const +{ + return nullptr; +} + +QStringList PluginManagerMock::getAllPluginNames(PluginType*) const +{ + return QStringList(); +} + +QStringList PluginManagerMock::getAllPluginNames() const +{ + return QStringList(); +} + +PluginType* PluginManagerMock::getPluginType(const QString&) const +{ + return nullptr; +} + +QString PluginManagerMock::getAuthor(const QString&) const +{ + return QString(); +} + +QString PluginManagerMock::getTitle(const QString&) const +{ + return QString(); +} + +QString PluginManagerMock::getPrintableVersion(const QString&) const +{ + return QString(); +} + +int PluginManagerMock::getVersion(const QString&) const +{ + return 3; +} + +QString PluginManagerMock::getDescription(const QString&) const +{ + return QString(); +} + +PluginType* PluginManagerMock::getPluginType(Plugin*) const +{ + return nullptr; +} + +QList PluginManagerMock::getLoadedPlugins(PluginType*) const +{ + return QList(); +} + +ScriptingPlugin* PluginManagerMock::getScriptingPlugin(const QString&) const +{ + return nullptr; +} + +void PluginManagerMock::registerPluginType(PluginType*) +{ +} + +QHash PluginManagerMock::readMetaData(const QJsonObject&) +{ + return QHash(); +} + +QString PluginManagerMock::toPrintableVersion(int) const +{ + return QString(); +} + + +QStringList PluginManagerMock::getDependencies(const QString&) const +{ + return QStringList(); +} + +QStringList PluginManagerMock::getConflicts(const QString&) const +{ + return QStringList(); +} + +QList PluginManagerMock::getAllPluginDetails() const +{ + return QList(); +} + +QList PluginManagerMock::getLoadedPluginDetails() const +{ + return QList(); +} + +QStringList PluginManagerMock::getLoadedPluginNames() const +{ + return QStringList(); +} + +bool PluginManagerMock::arePluginsInitiallyLoaded() const +{ + return true; +} + +QList PluginManagerMock::getLoadedPlugins() const +{ + return QList(); +} diff --git a/SQLiteStudio3/Tests/TestUtils/pluginmanagermock.h b/SQLiteStudio3/Tests/TestUtils/pluginmanagermock.h new file mode 100644 index 0000000..06e5b39 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/pluginmanagermock.h @@ -0,0 +1,46 @@ +#ifndef PLUGINMANAGERMOCK_H +#define PLUGINMANAGERMOCK_H + +#include "services/pluginmanager.h" + +class PluginManagerMock : public PluginManager +{ + public: + void init(); + void deinit(); + QList getPluginTypes() const; + QStringList getPluginDirs() const; + QString getFilePath(Plugin*) const; + bool loadBuiltInPlugin(Plugin*); + bool load(const QString&); + void unload(const QString&); + void unload(Plugin*); + bool isLoaded(const QString&) const; + bool isBuiltIn(const QString&) const; + Plugin*getLoadedPlugin(const QString&) const; + QStringList getAllPluginNames(PluginType*) const; + QStringList getAllPluginNames() const; + PluginType*getPluginType(const QString&) const; + QString getAuthor(const QString&) const; + QString getTitle(const QString&) const; + QString getPrintableVersion(const QString&) const; + int getVersion(const QString&) const; + QString getDescription(const QString&) const; + PluginType*getPluginType(Plugin*) const; + QList getLoadedPlugins(PluginType*) const; + ScriptingPlugin* getScriptingPlugin(const QString&) const; + QHash readMetaData(const QJsonObject&); + QString toPrintableVersion(int) const; + QStringList getDependencies(const QString&) const; + QStringList getConflicts(const QString&) const; + QList getAllPluginDetails() const; + QList getLoadedPluginDetails() const; + QStringList getLoadedPluginNames() const; + bool arePluginsInitiallyLoaded() const; + QList getLoadedPlugins() const; + + protected: + void registerPluginType(PluginType*); +}; + +#endif // PLUGINMANAGERMOCK_H diff --git a/SQLiteStudio3/Tests/TestUtils/test_common.pri b/SQLiteStudio3/Tests/TestUtils/test_common.pri new file mode 100644 index 0000000..c265ba1 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/test_common.pri @@ -0,0 +1,6 @@ +include($$PWD/../../dirs.pri) +include($$PWD/../testdirs.pri) + +CONFIG += c++11 + +LIBS += -lTestUtils -lcoreSQLiteStudio diff --git a/SQLiteStudio3/Tests/TestUtils/testutils_global.h b/SQLiteStudio3/Tests/TestUtils/testutils_global.h new file mode 100644 index 0000000..c4ddfa3 --- /dev/null +++ b/SQLiteStudio3/Tests/TestUtils/testutils_global.h @@ -0,0 +1,12 @@ +#ifndef TESTUTILS_GLOBAL_H +#define TESTUTILS_GLOBAL_H + +#include + +#if defined(TESTUTILS_LIBRARY) +# define TESTUTILSSHARED_EXPORT Q_DECL_EXPORT +#else +# define TESTUTILSSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // TESTUTILS_GLOBAL_H diff --git a/SQLiteStudio3/Tests/Tests.pro b/SQLiteStudio3/Tests/Tests.pro new file mode 100644 index 0000000..60ade33 --- /dev/null +++ b/SQLiteStudio3/Tests/Tests.pro @@ -0,0 +1,31 @@ +TEMPLATE = subdirs + +test_utils.subdir = TestUtils + +completion_helper.subdir = CompletionHelperTest +completion_helper.depends = test_utils + +select_resolver.subdir = SelectResolverTest +select_resolver.depends = test_utils + +parser.subdir = ParserTest +parser.depends = test_utils + +table_modifier.subdir = TableModifierTest +table_modifier.depends = test_utils + +hash_tables.subdir = HashTablesTest +hash_tables.depends = test_utils + +db_ver_conv.subdir = DbVersionConverterTest +db_ver_conv.depends = test_utils + +SUBDIRS += \ + test_utils \ + completion_helper \ + select_resolver \ + parser \ + table_modifier \ + hash_tables \ + db_ver_conv \ + DsvFormatsTest diff --git a/SQLiteStudio3/Tests/testdirs.pri b/SQLiteStudio3/Tests/testdirs.pri new file mode 100644 index 0000000..7e35f8d --- /dev/null +++ b/SQLiteStudio3/Tests/testdirs.pri @@ -0,0 +1,4 @@ +LIBS += -L$$PWD/../output/SQLiteStudio + +INCLUDEPATH += $$PWD/TestUtils +DEPENDPATH += $$PWD/TestUtils diff --git a/SQLiteStudio3/UpdateSQLiteStudio/UpdateSQLiteStudio.exe.manifest b/SQLiteStudio3/UpdateSQLiteStudio/UpdateSQLiteStudio.exe.manifest new file mode 100644 index 0000000..54081e5 --- /dev/null +++ b/SQLiteStudio3/UpdateSQLiteStudio/UpdateSQLiteStudio.exe.manifest @@ -0,0 +1,19 @@ + + + + SQLiteStudio updater + + + + + + + + + + + + diff --git a/SQLiteStudio3/UpdateSQLiteStudio/UpdateSQLiteStudio.pro b/SQLiteStudio3/UpdateSQLiteStudio/UpdateSQLiteStudio.pro new file mode 100644 index 0000000..93a7a59 --- /dev/null +++ b/SQLiteStudio3/UpdateSQLiteStudio/UpdateSQLiteStudio.pro @@ -0,0 +1,39 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-08-29T20:30:14 +# +#------------------------------------------------- + +include($$PWD/../dirs.pri) +include($$PWD/../utils.pri) + +QT += core +QT -= gui + +TARGET = UpdateSQLiteStudio +#CONFIG += console +CONFIG -= app_bundle + +CONFIG += c++11 +QMAKE_CXXFLAGS += -pedantic + +LIBS += -lcoreSQLiteStudio + +TEMPLATE = app + +linux|portable { + QMAKE_LFLAGS += -Wl,-rpath,./lib +} + + +win32: { + RC_FILE = windows.rc +} + +SOURCES += main.cpp + +OTHER_FILES += \ + windows.rc \ + UpdateSQLiteStudio.exe.manifest + +HEADERS += diff --git a/SQLiteStudio3/UpdateSQLiteStudio/main.cpp b/SQLiteStudio3/UpdateSQLiteStudio/main.cpp new file mode 100644 index 0000000..e5730d3 --- /dev/null +++ b/SQLiteStudio3/UpdateSQLiteStudio/main.cpp @@ -0,0 +1,49 @@ +#include "services/updatemanager.h" +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QString path = app.applicationDirPath() + QLatin1Char('/') + UpdateManager::WIN_INSTALL_FILE; + QFile installFile(path); + if (QFileInfo(path).isReadable()) + { + installFile.open(QIODevice::ReadOnly); + QTextStream inStr(&installFile); + QString option = inStr.readLine(); + QString backupDir = inStr.readLine(); + QString appDir = inStr.readLine(); + installFile.close(); + installFile.remove(); + + QString tempDir = app.applicationDirPath(); + if (option == UpdateManager::UPDATE_OPTION_NAME) + { + bool res = UpdateManager::executeFinalStep(tempDir, backupDir, appDir); + if (res) + { + QFile doneFile(appDir + QLatin1Char('/') + UpdateManager::WIN_UPDATE_DONE_FILE); + doneFile.open(QIODevice::WriteOnly); + doneFile.close(); + } + else + qCritical() << QString("Could not execute final step with root priviledges: %1").arg(UpdateManager::getStaticErrorMessage()); + } + else + { + qCritical() << QString("Option passed to updater not matched: '%1' != '%2'").arg(option, UpdateManager::UPDATE_OPTION_NAME); + } + } + else + { + qCritical() << QString("Updater installation file (%1) was not readable.").arg(path); + } + + return 0; +} diff --git a/SQLiteStudio3/UpdateSQLiteStudio/windows.manifest.autosave b/SQLiteStudio3/UpdateSQLiteStudio/windows.manifest.autosave new file mode 100644 index 0000000..788fba6 --- /dev/null +++ b/SQLiteStudio3/UpdateSQLiteStudio/windows.manifest.autosave @@ -0,0 +1,19 @@ + + + + Gokulnathvc Really Cool App + + + + + + + + + + + + \ No newline at end of file diff --git a/SQLiteStudio3/UpdateSQLiteStudio/windows.rc b/SQLiteStudio3/UpdateSQLiteStudio/windows.rc new file mode 100644 index 0000000..1e4ed25 --- /dev/null +++ b/SQLiteStudio3/UpdateSQLiteStudio/windows.rc @@ -0,0 +1,3 @@ +#include + +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "UpdateSQLiteStudio.exe.manifest" diff --git a/SQLiteStudio3/coreSQLiteStudio/TODO.txt b/SQLiteStudio3/coreSQLiteStudio/TODO.txt new file mode 100644 index 0000000..a86a49b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/TODO.txt @@ -0,0 +1,66 @@ +* Next versions: +- commiting DataView should be async +- syntax checkers as services - per language +- code assistants as services - per language +- specialized validation of expressions for DEFAULT constraint. +- "recovery" after failed startup - detecting if previous start crashed and if yes, propose cleaning of configuration. +- tcl highlighter +- plugin to do performance testing +- plugins to generate artifacts +- qtscript syntax checker +- tcl syntax checker +- dbf import +- dbf export +- code assistant as a service with plugins, so it can be extended with other langs and injected in custom functions window, collations window, etc +- in configuration dialog option to disable each individual native SQL function +- move "integrity check" into dedicated window, add "PRAGMA foreign_key_check" as a second stage of checking and present all in one window +- tips&tricks dialog +- crash management +- SqlEditor::refreshValidObjects() doesn't add valid object names from other databases (not yet attached). It might be tricky to implement. +- need an idea for some smart "revert" or "backup", so users are protected from accidentaly deleting table, or data in table. +- expose query executor steps chain to plugins +- complete plugin for "Search in database(s)" +- complete plugin for compare tables/databases +- executing queries with bind parameters +- completer: when suggesting table in FROM clause, look at columns after SELECT to give related tables first. +- constraints tab in table window should have toolbar for adding/editing/deleting constraints + +CLI: +- plugin management commands +- export commands +- import commands +- populate commands +- formatting command + +UNIT TESTS: +- Parser::parse: more complex examples, errors detecting +- Lexer::detokenize +- utils_sql (for splitQueries() check the CREATE TRIGGER) +- utils +- SelectResolver +- SchemaResolver + +* Advanced syntax error checks: +- many idxColumns are not allowed for column FK +- autoincrement not allowed for not integer type + + + + + + + + + +Qt mingw: +Not an easy task. I got Perl, Python, Ruby and MinGw/bin in path and +use this line + +configure -opensource -confirm-license -platform win32-g++ -make libs +-qt-libjpeg -qt-libpng -no-openssl -no-icu -qt-zlib -qt-pcre +-no-iconv -nomake examples -nomake tests -qt-style-windowsxp +-qt-style-windowsvista -opengl +desktop + +Qt linux: +./configure -no-icu -nomake examples -nomake tests -no-dbus -opensource -skip webkit -skip quickcontrols -prefix /home/spakowane/qt5.3.1/output diff --git a/SQLiteStudio3/coreSQLiteStudio/committable.cpp b/SQLiteStudio3/coreSQLiteStudio/committable.cpp new file mode 100644 index 0000000..892437a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/committable.cpp @@ -0,0 +1,41 @@ +#include "committable.h" +#include + +Committable::ConfirmFunction Committable::confirmFunc = nullptr; +QList Committable::instances; + +Committable::Committable() +{ + instances << this; +} + +Committable::~Committable() +{ + instances.removeOne(this); +} + +void Committable::init(Committable::ConfirmFunction confirmFunc) +{ + Committable::confirmFunc = confirmFunc; +} + +bool Committable::canQuit() +{ + if (!confirmFunc) + { + qCritical() << "No confirm function defined for Committable!"; + return true; + } + + QList uncommitedInstances; + for (Committable* c : instances) + { + if (c->isUncommited()) + uncommitedInstances << c; + } + + if (uncommitedInstances.size() == 0) + return true; + + return confirmFunc(uncommitedInstances); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/committable.h b/SQLiteStudio3/coreSQLiteStudio/committable.h new file mode 100644 index 0000000..cf1d48b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/committable.h @@ -0,0 +1,27 @@ +#ifndef COMMITTABLE_H +#define COMMITTABLE_H + +#include "coreSQLiteStudio_global.h" +#include +#include + +class API_EXPORT Committable +{ + public: + typedef std::function& instances)> ConfirmFunction; + + Committable(); + virtual ~Committable(); + + virtual bool isUncommited() const = 0; + virtual QString getQuitUncommitedConfirmMessage() const = 0; + + static void init(ConfirmFunction confirmFunc); + static bool canQuit(); + + private: + static ConfirmFunction confirmFunc; + static QList instances; +}; + +#endif // COMMITTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/bihash.h b/SQLiteStudio3/coreSQLiteStudio/common/bihash.h new file mode 100644 index 0000000..cde93d1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/bihash.h @@ -0,0 +1,302 @@ +#ifndef BIHASH_H +#define BIHASH_H + +#include + +/** + * @brief Bi-directional QHash + * + * Bi-directional hash treats both inserted values as keys to each other. + * Bi-directional hash built on the left and right values concept. + * + * It's not multi-value hash, so when you try to insert existing value to any + * of sides (left or right), it will replace the whole conflicting entry. + * + * It doesn't provide operator[], because returning reference to an object, + * which then can be changed outside would desynchronize internals of this hash + * (internal inverted map could not be synchronized properly according to changes + * to the external reference). + */ +template +class BiHash +{ + public: + /** + * @brief Creates empty hash. + */ + BiHash() {} + + /** + * @brief Creates hash initialized with given values. + * @param list C++11 style initializer list, like: {{x=y}, {a=b}} + */ + BiHash(std::initializer_list> list) + { + hash = QHash(list); + initInverted(); + } + + /** + * @brief Creates bi-hash basing on QHash. + * @param other QHash to copy data from. + * + * If multiple keys in the QHash refer to the same value, + * this is not defined which one of those values will remain, + * but there will definitely be copied only one of them. + */ + BiHash(const QHash & other) + { + unite(other); + } + + /** + * @brief Creates copy of BiHash. + * @param other BiHash to copy. + */ + BiHash(const BiHash & other) : hash(other.hash), inverted(other.inverted) {} + + /** + * @brief Inserts new values to the hash. + * @param left Left value to be inserted. + * @param right Right value to be inserted. + * + * Note that if the \p left is already in left values, then the current entry + * for the left will be replaced with this new one. + * The same applies for the \p right. + * + * If it happens, that both \p left and \p right have already entries in the + * hash, but those are 2 different entries, then the insert operation will + * remove both conflicting records and insert the new one. + */ + void insert(const L& left, const R& right) + { + if (hash.contains(left)) + removeLeft(left); + + if (inverted.contains(right)) + removeRight(right); + + inverted.insert(right, left); + hash.insert(left, right); + } + + /** + * @brief Tests if left values contain given value. + * @param left Value to test. + * @return true if left values containe the \p left value. + */ + bool containsLeft(const L& left) const + { + return hash.contains(left); + } + + /** + * @brief Tests if right values contain given value. + * @param right Value to test. + * @return true if right values containe the \p right value. + */ + bool containsRight(const R& right) const + { + return inverted.contains(right); + } + + /** + * @brief Removes entry with left value equal to given value. + * @param left Value to remove by. + * @return Number of removed entries. + */ + int removeLeft(const L& left) + { + if (!hash.contains(left)) + return 0; + + inverted.remove(hash.value(left)); + hash.remove(left); + + return 1; + } + + /** + * @brief Removes entry with right value equal to given value. + * @param right Value to remove by. + * @return Number of removed entries. + */ + int removeRight(const R& right) + { + if (!inverted.contains(right)) + return 0; + + hash.remove(inverted.value(right)); + inverted.remove(right); + + return 1; + } + + /** + * @brief Pops entry with left value equal to given value. + * @param left Value to pop by. + * @return Right value assigned to given left value. + */ + R takeLeft(const L& left) + { + R right = hash.take(left); + inverted.remove(right); + return right; + } + + /** + * @brief Pops entry with right value equal to given value. + * @param right Value to pop by. + * @return Left value assigned to given right value. + */ + L takeRight(const R& right) + { + R left = inverted.take(right); + hash.remove(left); + return left; + } + + /** + * @brief Copies all entries from other BiHash to this hash. + * @param other BiHash to copy from. + * @return Reference to this hash after values are copied. + * + * Any entries from the \p other hash will overwrite current + * entries in case of conflict (by either left or right value). + */ + BiHash& unite(const BiHash& other) + { + unite(other.hash); + return *this; + } + + /** + * @overload + */ + BiHash& unite(const QHash& other) + { + QHashIterator it(other); + while (it.hasNext()) + insert(it.next().key(), it.value()); + + return *this; + } + + /** + * @brief Finds right value associated with given left value. + * @param left Left value to match. + * @return Right value if found, or default constructed value of right type. + */ + R valueByLeft(const L& left) const + { + return hash.value(left); + } + + /** + * @brief Finds left value associated with given right value. + * @param right Right value to match. + * @return Left value if found, or default constructed value of left type. + */ + L valueByRight(const R& right) const + { + return inverted.value(right); + } + + /** + * @brief Provides all left values. + * @return List of values from left side. + */ + QList leftValues() const + { + return hash.keys(); + } + + /** + * @brief Provides all right values. + * @return List of values from right side. + */ + QList rightValues() const + { + return inverted.keys(); + } + + /** + * @brief Provides java-like iterator for the hash. + * @return Iterator object for this hash. + */ + QHashIterator iterator() const + { + return QHashIterator(hash); + } + + /** + * @brief Removes all entries from the hash. + */ + void clear() + { + hash.clear(); + inverted.clear(); + } + + /** + * @brief Counts all entries in the hash. + * @return Number of entries. + */ + int count() const + { + return hash.count(); + } + + /** + * @brief Tests whether the hash is empty or not. + * @return true if the hash is empty, false otherwise. + */ + bool isEmpty() const + { + return hash.isEmpty(); + } + + /** + * @brief Provides QHash from this BiHash. + * @return QHash with left values as keys. + */ + const QHash& toQHash() const + { + return hash; + } + + /** + * @brief Provides QHash with inverted values (right-to-left) + * @return QHash with right values as keys. + */ + const QHash& toInvertedQHash() const + { + return inverted; + } + + private: + /** + * @brief Fills inverted internal hash basing on values from hash class member. + */ + void initInverted() + { + QHashIterator it(hash); + while (it.hasNext()) + { + it.next(); + inverted[it.value()] = it.key(); + } + } + + /** + * @brief Hash containing left-to-right mapping. + */ + QHash hash; + + /** + * @brief Hash containing right-to-left mapping. + */ + QHash inverted; +}; + +#endif // BIHASH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h b/SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h new file mode 100644 index 0000000..65c907b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h @@ -0,0 +1,362 @@ +#ifndef BISTRHASH_H +#define BISTRHASH_H + +#include "bihash.h" +#include +#include + +/** + * @brief Bi-directional string-oriented hash. + * + * This hash is very similar to BiHash, except it always uses QString + * for both left and right values. Given that, it also provides Qt::CaseSensitivity support + * for any operations accepting values in parameters. + * + * Just like BiHash, the BiStrHash doesn't provide operator[]. For more details see BiHash. + */ +class BiStrHash +{ + public: + /** + * @brief Creates empty hash. + */ + BiStrHash() {} + + /** + * @brief Creates pre-initialized hash. + * @param list C++11 style initializer list, like: {{"x"="y"}, {"a"="b"}} + */ + BiStrHash(std::initializer_list> list) + { + hash = QHash(list); + initInvertedAndLower(); + } + + /** + * @brief Creates BiStrHash basing on QHash. + * @param other QHash to copy values from. + * + * Any conflicting values from the \p other hash will overwrite + * current values in the hash. + */ + BiStrHash(const QHash & other) + { + unite(other); + } + + /** + * @brief Copy constructor. + * @param other Other hash to copy. + */ + BiStrHash(const BiStrHash& other) : hash(other.hash), inverted(other.inverted), + lowerHash(other.lowerHash), lowerInverted(other.lowerInverted) {} + + /** + * @brief Inserts entry into the hash. + * @param left Left-side value to insert. + * @param right Right-side value to insert. + * + * Inserting to the hash is done in case-insensitive manner, hence any conflicting + * values matched with case insensitive method will be replaced with the new entry. + */ + void insert(const QString& left, const QString& right) + { + if (lowerHash.contains(left.toLower())) + removeLeft(left, Qt::CaseInsensitive); + + if (lowerInverted.contains(right.toLower())) + removeRight(right, Qt::CaseInsensitive); + + inverted.insert(right, left); + hash.insert(left, right); + lowerHash.insert(left.toLower(), left); + lowerInverted.insert(right.toLower(), right); + } + + /** + * @brief Tests if given value is in the left values of the hash. + * @param left Left-side value to match. + * @param cs Case sensitivity flag. + * @return true if the key was matched in left side values, or false otherwise. + */ + bool containsLeft(const QString& left, Qt::CaseSensitivity cs = Qt::CaseSensitive) + { + if (cs == Qt::CaseSensitive) + return hash.contains(left); + else + return lowerHash.contains(left.toLower()); + } + + /** + * @brief Tests if given value is in the right values of the hash. + * @param right Right-side value to match. + * @param cs Case sensitivity flag. + * @return true if the key was matched in right side values, or false otherwise. + */ + bool containsRight(const QString& right, Qt::CaseSensitivity cs = Qt::CaseSensitive) + { + if (cs == Qt::CaseSensitive) + return inverted.contains(right); + else + return lowerInverted.contains(right.toLower()); + } + + /** + * @brief Removes entry matching given value in left-side values. + * @param left Left-side value to match. + * @param cs Case sensitivity flag. + * @return Number of entries removed. + */ + int removeLeft(const QString& left, Qt::CaseSensitivity cs = Qt::CaseSensitive) + { + if (cs == Qt::CaseSensitive) + { + if (!hash.contains(left)) + return 0; + + inverted.remove(hash.value(left)); + hash.remove(left); + + return 1; + } + else + { + QString lowerLeft = left.toLower(); + if (!lowerHash.contains(lowerLeft)) + return 0; + + QString right = hash.value(lowerHash.value(lowerLeft)); + + hash.remove(inverted.value(right)); + inverted.remove(right); + lowerHash.remove(lowerLeft); + lowerInverted.remove(right.toLower()); + + return 1; + } + } + + /** + * @brief Removes entry matching given value in right-side values. + * @param right Right-side value to match. + * @param cs Case sensitivity flag. + * @return Number of entries removed. + */ + int removeRight(const QString& right, Qt::CaseSensitivity cs = Qt::CaseSensitive) + { + if (cs == Qt::CaseSensitive) + { + if (!inverted.contains(right)) + return 0; + + hash.remove(inverted.value(right)); + inverted.remove(right); + + return 1; + } + else + { + QString lowerRight = right.toLower(); + if (!lowerInverted.contains(lowerRight)) + return 0; + + QString left = inverted.value(lowerInverted.value(lowerRight)); + + inverted.remove(hash.value(left)); + hash.remove(left); + lowerHash.remove(left.toLower()); + lowerInverted.remove(lowerRight); + + return 1; + } + } + + /** + * @brief Removes entry from hash and returns it. + * @param left Left-side value to match. + * @param cs Case sensitivity flag. + * @return Right side value, or null string if the \p left was not matched. + */ + QString takeLeft(const QString& left, Qt::CaseSensitivity cs = Qt::CaseSensitive) + { + if (cs == Qt::CaseSensitive) + { + QString right = hash.take(left); + inverted.remove(right); + return right; + } + else + { + QString right = hash.take(lowerHash.take(left.toLower())); + inverted.remove(lowerInverted.take(right.toLower())); + return right; + } + } + + /** + * @brief Removes entry from hash and returns it. + * @param right Right-side value to match. + * @param cs Case sensitivity flag. + * @return Left side value, or null string if the \p left was not matched. + */ + QString takeRight(const QString& right, Qt::CaseSensitivity cs = Qt::CaseSensitive) + { + if (cs == Qt::CaseSensitive) + { + QString left = inverted.take(right); + hash.remove(left); + return left; + } + else + { + QString left = inverted.take(lowerInverted.take(right.toLower())); + hash.remove(lowerHash.take(left.toLower())); + return left; + } + } + + /** + * @brief Copies all entries from the other hash to this hash. + * @param other Other hash to copy values from. + * @return Reference to this hash, after update. + */ + BiStrHash& unite(const BiStrHash& other) + { + unite(other.hash); + return *this; + } + + /** + * @overload + */ + BiStrHash& unite(const QHash& other) + { + QHashIterator it(other); + while (it.hasNext()) + insert(it.next().key(), it.value()); + + return *this; + } + + /** + * @brief Finds right-side value by matching the left-side value. + * @param left Left-side value to match. + * @param cs Case sensitivity flag. + * @return Right-side value, or null string if left-side value was not matched. + */ + QString valueByLeft(const QString& left, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + { + if (cs == Qt::CaseSensitive) + return hash.value(left); + else + return hash.value(lowerHash.value(left.toLower())); + } + + /** + * @brief Finds left-side value by matching the right-side value. + * @param right Right-side value to match. + * @param cs Case sensitivity flag. + * @return Left-side value, or null string if right-side value was not matched. + */ + QString valueByRight(const QString& right, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + { + if (cs == Qt::CaseSensitive) + return inverted.value(right); + else + return inverted.value(lowerInverted.value(right.toLower())); + } + + /** + * @brief Gives all left-side values. + * @return List of values. + */ + QStringList leftValues() const + { + return hash.keys(); + } + + /** + * @brief Gives all right-side values. + * @return List of values. + */ + QStringList rightValues() const + { + return inverted.keys(); + } + + /** + * @brief Provides java-style iterator for this hash. + * @return Iterator object. + */ + QHashIterator iterator() const + { + return QHashIterator(hash); + } + + /** + * @brief Removes all entries from the hash. + */ + void clear() + { + hash.clear(); + inverted.clear(); + lowerHash.clear(); + lowerInverted.clear(); + } + + /** + * @brief Counts all entries in the hash. + * @return Number of entries in the hash. + */ + int count() const + { + return hash.count(); + } + + /** + * @brief Tests whether the hash is empty or not. + * @return true if the hash is empty, false otherwise. + */ + bool isEmpty() const + { + return hash.isEmpty(); + } + + private: + /** + * @brief Fills inverted and lower internal hashes basing on the main hash class member. + */ + void initInvertedAndLower() + { + QHashIterator it(hash); + while (it.hasNext()) + { + it.next(); + inverted[it.value()] = it.key(); + lowerHash[it.key().toLower()] = it.key(); + lowerInverted[it.value().toLower()] = it.value(); + } + } + + /** + * @brief Standard mapping - left to right. + */ + QHash hash; + + /** + * @brief Right to left mapping. + */ + QHash inverted; + + /** + * @brief Lower left to true left key mapping. + */ + QHash lowerHash; + + /** + * @brief Lower right to true right key mapping. + */ + QHash lowerInverted; +}; + +#endif // BISTRHASH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/column.cpp b/SQLiteStudio3/coreSQLiteStudio/common/column.cpp new file mode 100644 index 0000000..cc282e6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/column.cpp @@ -0,0 +1,38 @@ +#include "column.h" +#include + +Column::Column() : Table() +{ +} + +Column::Column(const QString& database, const QString& table, const QString& column) : + Table(database, table) +{ + setColumn(column); +} + +Column::Column(const Column& other) : + Table(other.database, other.table) +{ + column = other.column; +} + +int Column::operator ==(const Column& other) const +{ + return Table::operator==(other) && column == other.column; +} + +QString Column::getColumn() const +{ + return column; +} + +void Column::setColumn(const QString& value) +{ + column = value; +} + +int qHash(Column column) +{ + return qHash(column.getDatabase() + "." + column.getTable() + "." + column.getColumn()); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/column.h b/SQLiteStudio3/coreSQLiteStudio/common/column.h new file mode 100644 index 0000000..18e5edd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/column.h @@ -0,0 +1,26 @@ +#ifndef COLUMN_H +#define COLUMN_H + +#include "table.h" +#include "coreSQLiteStudio_global.h" +#include + +struct API_EXPORT Column : public Table +{ + public: + Column(); + Column(const QString& database, const QString& table, const QString& column); + Column(const Column& other); + + int operator ==(const Column& other) const; + + QString getColumn() const; + void setColumn(const QString& value); + + private: + QString column; +}; + +int API_EXPORT qHash(Column column); + +#endif // COLUMN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/global.h b/SQLiteStudio3/coreSQLiteStudio/common/global.h new file mode 100644 index 0000000..bc228ca --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/global.h @@ -0,0 +1,66 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +/** @file */ + +#define DEEP_COPY_FIELD(T, F) \ + if (other.F) \ + { \ + F = new T(*other.F); \ + F->setParent(this); \ + } + +#define DEEP_COPY_COLLECTION(T, F) \ + T* _new##T; \ + foreach (T* _element, other.F) \ + { \ + _new##T = new T(*_element); \ + _new##T->setParent(this); \ + F << _new##T; \ + } + +/** + * @brief Deletes object and sets the pointer to null. + * + * Deletes object under given pointer, but only if the pointer is not null. + * Also sets the pointer to the null after deleting is done. + */ +#define safe_delete(var) \ + if (var) \ + { \ + delete var; \ + var = nullptr; \ + } + +#define static_char static constexpr const char + +#define static_qstring(N,V) const static QString N = QStringLiteral(V) + +#define DECLARE_SINGLETON(Cls) \ + public: \ + static Cls* getInstance(); \ + static void destroy(); \ + \ + private: \ + static Cls* _instance; + +#define DEFINE_SINGLETON(Cls) \ + Cls* Cls::_instance = nullptr; \ + \ + Cls* Cls::getInstance() \ + { \ + if (!_instance) \ + _instance = new Cls(); \ + \ + return _instance; \ + } \ + \ + void Cls::destroy() \ + { \ + safe_delete(_instance); \ + } + +#define STRINGIFY(s) _STRINGIFY(s) +#define _STRINGIFY(s) #s + +#endif // GLOBAL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.cpp b/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.cpp new file mode 100644 index 0000000..386c65c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.cpp @@ -0,0 +1,83 @@ +#include "memoryusage.h" +#include + +#ifdef Q_OS_LINUX +#include +#include +#else + +#ifdef Q_OS_WIN32 +#include "windows.h" +#include "psapi.h" +#else + +#ifdef Q_OS_MAC +#include +#endif // Q_OS_MAC + +#endif // Q_OS_WIN32 +#endif // Q_OS_LINUX + +#ifdef Q_OS_LINUX + +int getMemoryUsage() +{ + static const QRegularExpression re("VmSize\\:\\s+(\\d+)\\s+(\\w+)"); + + QFile file("/proc/self/status"); + if (!file.open(QIODevice::ReadOnly)) + return -1; + + QString contents = file.readAll(); + QRegularExpressionMatch match = re.match(contents); + if (!match.hasMatch()) + return -1; + + bool ok; + int result = match.captured(1).toInt(&ok); + if (!ok) + return -1; + + QString unit = match.captured(2).toLower(); + if (unit == "mb") + return result * 1024 * 1024; + + if (unit == "kb") + return result * 1024; + + return result; +} + +#else +#ifdef Q_OS_WIN32 + +int getMemoryUsage() +{ + PROCESS_MEMORY_COUNTERS_EX pmc; + GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)); + return pmc.PrivateUsage; +} + +#else +#ifdef Q_OS_MAC + +int getMemoryUsage() +{ + struct task_basic_info t_info; + mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; + + if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count)) + return -1; + + return t_info.virtual_size; +} + +#else +int getMemoryUsage() +{ + return -1; +} + +#endif // Q_OS_MAC +#endif // Q_OS_WIN32 +#endif // Q_OS_LINUX diff --git a/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.h b/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.h new file mode 100644 index 0000000..55edfdb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.h @@ -0,0 +1,6 @@ +#ifndef MEMORYUSAGE_H +#define MEMORYUSAGE_H + +int getMemoryUsage(); + +#endif // MEMORYUSAGE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.cpp b/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.cpp new file mode 100644 index 0000000..304aa9d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.cpp @@ -0,0 +1,19 @@ +#include "nulldevice.h" + +NullDevice::NullDevice(QObject *parent) : + QIODevice(parent) +{ +} + +qint64 NullDevice::readData(char *data, qint64 maxSize) +{ + (void)(data); // slicence unused var + (void)(maxSize); // slicence unused var + return 0; +} + +qint64 NullDevice::writeData(const char *data, qint64 maxSize) +{ + (void)(data); // slicence unused var + return maxSize; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h b/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h new file mode 100644 index 0000000..d714114 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h @@ -0,0 +1,15 @@ +#ifndef NULLDEVICE_H +#define NULLDEVICE_H + +#include + +class NullDevice : public QIODevice +{ + public: + explicit NullDevice(QObject *parent = 0); + + qint64 readData(char * data, qint64 maxSize); + qint64 writeData(const char * data, qint64 maxSize); +}; + +#endif // NULLDEVICE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/objectpool.h b/SQLiteStudio3/coreSQLiteStudio/common/objectpool.h new file mode 100644 index 0000000..b9f6b9f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/objectpool.h @@ -0,0 +1,84 @@ +#ifndef OBJECTPOOL_H +#define OBJECTPOOL_H + +#include +#include +#include +#include + +template +class ObjectPool +{ + public: + ObjectPool(quint32 min, quint32 max); + + T* reserve(); + void release(T* obj); + + private: + QHash pool; + QMutex mutex; + QWaitCondition waitCond; + int min; + int max; +}; + +template +ObjectPool::ObjectPool(quint32 min, quint32 max) + : min(min), max(max) +{ + Q_ASSERT(min > 0); + T* obj = nullptr; + for (int i = 0; i < min; i++) + { + obj = new T(); + pool[obj] = false; + } +} + +T* ObjectPool::reserve() +{ + mutex.lock(); + + forever + { + QHashIterator i(pool); + while (i.hasNext()) + { + i.next(); + if (!i.value()) + { + pool[i.key()] = true; + T* obj = i.key(); + mutex.unlock(); + return obj; + } + } + + // Check if we can enlarge the pool + if (pool.size() < max) + { + T* obj = new T(); + pool[i.key()] = true; + mutex.unlock(); + return obj; + } + + // Wait for release + waitCond.wait(&mutex); + } + + // no need to unlock, because the loop will repeat + // until the free obj is found and then mutex is unlocked. +} + +template +void ObjectPool::release(T* obj) +{ + mutex.lock(); + pool[obj] = false; + mutex.unlock(); + waitCond.wakeOne(); +} + +#endif // OBJECTPOOL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp new file mode 100644 index 0000000..0eaca75 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp @@ -0,0 +1,103 @@ +#include "readwritelocker.h" +#include "parser/lexer.h" +#include +#include +#include + +ReadWriteLocker::ReadWriteLocker(QReadWriteLock* lock, Mode mode) +{ + init(lock, mode); +} + +ReadWriteLocker::ReadWriteLocker(QReadWriteLock* lock, const QString& query, Dialect dialect, bool noLock) +{ + init(lock, getMode(query, dialect, noLock)); +} + +ReadWriteLocker::~ReadWriteLocker() +{ + if (readLocker) + { + delete readLocker; + readLocker = nullptr; + } + + if (writeLocker) + { + delete writeLocker; + writeLocker = nullptr; + } +} + +void ReadWriteLocker::init(QReadWriteLock* lock, ReadWriteLocker::Mode mode) +{ + switch (mode) + { + case ReadWriteLocker::READ: + readLocker = new QReadLocker(lock); + break; + case ReadWriteLocker::WRITE: + writeLocker = new QWriteLocker(lock); + break; + case ReadWriteLocker::NONE: + // Nothing to lock. + break; + } +} + +ReadWriteLocker::Mode ReadWriteLocker::getMode(const QString &query, Dialect dialect, bool noLock) +{ + static QStringList readOnlyCommands = {"ANALYZE", "EXPLAIN", "PRAGMA"}; + + if (noLock) + return ReadWriteLocker::NONE; + + TokenList tokens = Lexer::tokenize(query, dialect); + int keywordIdx = tokens.indexOf(Token::KEYWORD); + + if (keywordIdx > -1 && readOnlyCommands.contains(tokens[keywordIdx]->value.toUpper())) + return ReadWriteLocker::READ; + + if (keywordIdx > -1 && tokens[keywordIdx]->value.toUpper() == "WITH") + { + bool matched = false; + bool isSelect = false; + int depth = 0; + for (TokenPtr token : tokens) + { + switch (token->type) + { + case Token::PAR_LEFT: + depth++; + break; + case Token::PAR_RIGHT: + depth--; + break; + case Token::KEYWORD: + if (depth == 0) + { + QString val = token->value.toUpper(); + if (val == "SELECT") + { + matched = true; + isSelect = true; + } + else if (val == "DELETE" || val == "UPDATE" || val == "INSERT") + { + matched = true; + } + } + break; + default: + break; + } + + if (matched) + break; + } + if (isSelect) + return ReadWriteLocker::READ; + } + + return ReadWriteLocker::WRITE; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.h b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.h new file mode 100644 index 0000000..cac9368 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.h @@ -0,0 +1,65 @@ +#ifndef READWRITELOCKER_H +#define READWRITELOCKER_H + +#include "coreSQLiteStudio_global.h" +#include "dialect.h" + +class QReadLocker; +class QWriteLocker; +class QReadWriteLock; + +/** + * @brief The ReadWriteLocker class + * + * This class behaves pretty much like QReadLocker or QWriteLocker + * (it actually uses those internally), except it can be either + * of those and this is to be decided at the moment of creation. + * Therefore the locker can work as read or write locker depending + * on an external condition. + * There's also a possibility to not lock anything as a third + * choice of working mode, so this also can be decided + * at construction moment. + */ +class API_EXPORT ReadWriteLocker +{ + public: + enum Mode + { + READ, + WRITE, + NONE + }; + + ReadWriteLocker(QReadWriteLock* lock, Mode mode); + ReadWriteLocker(QReadWriteLock* lock, const QString& query, Dialect dialect, bool noLock); + virtual ~ReadWriteLocker(); + + /** + * @brief Provides required locking mode for given query. + * @param query Query to be executed. + * @return Locking mode: READ or WRITE. + * + * Given the query this method analyzes what is the query and provides information if the query + * will do some changes on the database, or not. Then it returns proper locking mode that should + * be used for this query execution. + * + * Query execution methods from this class check if lock mode of the query to be executed isn't + * in conflict with the lock being currently applied on the dbOperLock (if any is applied at the moment). + * + * This method works on a very simple rule. It assumes that queries: SELECT, ANALYZE, EXPLAIN, + * and PRAGMA - are read-only, while all other queries are read-write. + * In case of PRAGMA this is not entirely true, but it's not like using PRAGMA for changing + * some setting would cause database state inconsistency. At least not from perspective of SQLiteStudio. + * + * In case of WITH statement it filters out the "WITH clause" and then checks for SELECT keyword. + */ + static ReadWriteLocker::Mode getMode(const QString& query, Dialect dialect, bool noLock); + + private: + void init(QReadWriteLock* lock, Mode mode); + + QReadLocker* readLocker = nullptr; + QWriteLocker* writeLocker = nullptr; +}; + +#endif // READWRITELOCKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h b/SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h new file mode 100644 index 0000000..9ca6ade --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h @@ -0,0 +1,164 @@ +#ifndef SORTEDHASH_H +#define SORTEDHASH_H + +#include + +/** + * @brief Partially implemented sorted hash. + * + * This is kind of a sorted QHash, except it doesn't act as sorted with iterators, + * just with keys. It also doesn't work with multiple values for single key. + * + * The complete sorted hash might be implemented later on. + */ +template +class SortedHash : public QHash +{ + public: + SortedHash(std::initializer_list> list) : QHash(list) + { + sortedKeys = keys(); + } + + SortedHash(const QHash& other) : QHash(other) + { + sortedKeys = keys(); + } + + SortedHash(QHash&& other) : QHash(other) + { + sortedKeys = keys(); + } + + SortedHash() : QHash() + { + } + + typename QHash::iterator insert(const Key& key, const T& value) + { + if (!sortedKeys.contains(key)) + sortedKeys << key; + + return QHash::insert(key, value); + } + + int remove(const Key& key) + { + sortedKeys.removeOne(key); + return QHash::remove(key); + } + + void swap(QHash& other) + { + QHash::swap(other); + sortedKeys = keys(); + } + + void swap(SortedHash& other) + { + QHash::swap(other); + sortedKeys = other.sortedKeys; + } + + T take(const Key& key) + { + sortedKeys.removeOne(key); + return QHash::take(key); + } + + QList keys() const + { + return sortedKeys; + } + + QList keys(const T& value) const + { + QList results; + foreach (const Key& k, sortedKeys) + if (value(k) == value) + results << k; + + return results; + } + + SortedHash& unite(const QHash& other) + { + QHash::unite(other); + sortedKeys += other.keys(); + return *this; + } + + SortedHash& unite(const SortedHash& other) + { + QHash::unite(other); + sortedKeys += other.sortedKeys; + return *this; + } + + QList values() const + { + QList results; + foreach (const Key& k, sortedKeys) + results << value(k); + + return results; + } + + bool operator!=(const SortedHash& other) const + { + return !operator==(other); + } + + SortedHash& operator=(const QHash& other) + { + QHash::operator=(other); + sortedKeys = other.keys(); + return *this; + } + + SortedHash& operator=(const SortedHash& other) + { + QHash::operator=(other); + sortedKeys = other.sortedKeys; + return *this; + } + + bool operator==(const SortedHash& other) const + { + return QHash::operator==(other) && sortedKeys == other.sortedKeys; + } + + T & operator[](const Key& key) + { + if (!sortedKeys.contains(key)) + sortedKeys << key; + + return QHash::operator[](key); + } + + const T operator[](const Key& key) const + { + return QHash::operator[](key); + } + + Key firstKey() const + { + if (sortedKeys.size() == 0) + return Key(); + + return sortedKeys.first(); + } + + Key lastKey() const + { + if (sortedKeys.size() == 0) + return Key(); + + return sortedKeys.last(); + } + + private: + QList sortedKeys; +}; + +#endif // SORTEDHASH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/strhash.h b/SQLiteStudio3/coreSQLiteStudio/common/strhash.h new file mode 100644 index 0000000..4fb7bb3 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/strhash.h @@ -0,0 +1,182 @@ +#ifndef STRHASH_H +#define STRHASH_H + +#include +#include +#include +#include + +template +class StrHash +{ + public: + StrHash() {} + StrHash(std::initializer_list> list) : hash(QHash(list)) + { + initLower(); + } + + StrHash(const QHash& other) : hash(QHash(other)) + { + initLower(); + } + + void insert(const QString& key, const T& value) + { + if (lowerCaseHash.contains(key.toLower())) + remove(key, Qt::CaseInsensitive); + + hash.insert(key, value); + lowerCaseHash.insert(key.toLower(), key); + } + + bool contains(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + { + if (cs == Qt::CaseSensitive) + return hash.contains(key); + + return lowerCaseHash.contains(key.toLower()); + } + + int remove(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive) + { + if (cs == Qt::CaseSensitive) + { + int res = hash.remove(key); + if (res > 0) + lowerCaseHash.remove(key.toLower()); + + return res; + } + + // Case insensitive + QString lowerKey = key.toLower(); + if (lowerCaseHash.contains(lowerKey)) + { + int res = hash.remove(lowerCaseHash.value(lowerKey)); + lowerCaseHash.remove(lowerKey); + return res; + } + + return 0; + } + + T take(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive) + { + if (cs == Qt::CaseSensitive) + { + lowerCaseHash.remove(key.toLower()); + return hash.take(key); + } + + // Case insensitive + QString lowerKey = key.toLower(); + if (lowerCaseHash.contains(lowerKey)) + { + QString theKey = lowerCaseHash.value(lowerKey); + lowerCaseHash.remove(lowerKey); + return hash.take(theKey); + } + + return QString(); + } + + StrHash& unite(const StrHash& other) + { + unite(other.hash); + return *this; + } + + StrHash& unite(const QHash& other) + { + QHashIterator it(other); + while (it.hasNext()) + { + it.next(); + insert(it.key(), it.value()); + } + + return *this; + } + + T value(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + { + if (cs == Qt::CaseSensitive) + return hash.value(key); + + return hash.value(lowerCaseHash.value(key.toLower())); + } + + QList values() const + { + return hash.values(); + } + + QStringList keys() const + { + return hash.keys(); + } + + QHashIterator iterator() const + { + return QHashIterator(hash); + } + + void clear() + { + hash.clear(); + lowerCaseHash.clear(); + } + + int count() const + { + return hash.count(); + } + + int count(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + { + if (cs == Qt::CaseSensitive) + return hash.count(key); + + return lowerCaseHash.count(key.toLower()); + } + + bool isEmpty() const + { + return hash.isEmpty(); + } + + T& operator[](const QString& key) + { + if (lowerCaseHash.contains(key.toLower()) && !hash.contains(key)) + { + T value = hash[lowerCaseHash[key.toLower()]]; + remove(key, Qt::CaseInsensitive); + hash[key] = value; + } + + lowerCaseHash[key.toLower()] = key; + return hash[key]; + } + + const T operator[](const QString& key) const + { + return hash[lowerCaseHash[key.toLower()]]; + } + + private: + void initLower() + { + QHashIterator it(hash); + while (it.hasNext()) + { + it.next(); + lowerCaseHash[it.key().toLower()] = it.key(); + } + } + + QHash lowerCaseHash; + QHash hash; +}; + +#endif // STRHASH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/table.cpp b/SQLiteStudio3/coreSQLiteStudio/common/table.cpp new file mode 100644 index 0000000..c590995 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/table.cpp @@ -0,0 +1,52 @@ +#include "table.h" +#include + +Table::Table() +{ +} + +Table::Table(const QString& database, const QString& table) +{ + setDatabase(database); + setTable(table); +} + +Table::Table(const Table& other) +{ + database = other.database; + table = other.table; +} + +Table::~Table() +{ +} + +int Table::operator ==(const Table &other) const +{ + return other.database == this->database && other.table == this->table; +} + +QString Table::getTable() const +{ + return table; +} + +void Table::setTable(const QString& value) +{ + table = value; +} + +QString Table::getDatabase() const +{ + return database; +} + +void Table::setDatabase(const QString& value) +{ + database = value.isEmpty() ? "main" : value; +} + +int qHash(Table table) +{ + return qHash(table.getDatabase() + "." + table.getTable()); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/table.h b/SQLiteStudio3/coreSQLiteStudio/common/table.h new file mode 100644 index 0000000..d17a729 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/table.h @@ -0,0 +1,32 @@ +#ifndef TABLE_H +#define TABLE_H + +#include "coreSQLiteStudio_global.h" +#include + +class API_EXPORT Table +{ + public: + Table(); + Table(const QString& database, const QString& table); + Table(const Table& other); + virtual ~Table(); + + int operator ==(const Table& other) const; + + QString getDatabase() const; + void setDatabase(const QString& value); + + QString getTable() const; + void setTable(const QString& value); + + protected: + QString database; + QString table; + +}; + +int API_EXPORT qHash(Table table); + + +#endif // TABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/unused.h b/SQLiteStudio3/coreSQLiteStudio/common/unused.h new file mode 100644 index 0000000..090a8a2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/unused.h @@ -0,0 +1,6 @@ +#ifndef UNUSED_H +#define UNUSED_H + +#define UNUSED(X) (void)(X) + +#endif // UNUSED_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp b/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp new file mode 100644 index 0000000..d56d838 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp @@ -0,0 +1,855 @@ +#include "common/utils.h" +#include "common/global.h" +#include "dbobjecttype.h" +#include "rsa/RSA.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_LINUX +#include + +#include +#endif + +void initUtils() +{ + qRegisterMetaType>("QList"); + qRegisterMetaType("DbObjectType"); +} + +bool isXDigit(const QChar& c) +{ + return c.isDigit() || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +QChar charAt(const QString& str, int pos) +{ + if (pos < 0 || pos >= str.size()) + return QChar(0); + + return str[pos]; +} + +int rand(int min, int max) +{ + return qrand() % (max-min) + min; +} + +QString randStr(int length, bool numChars, bool whiteSpaces) +{ + static const char* alphaNumChars = " abcdefghijklmnopqrstuvwxyz1234567890"; + int start = 1; + int range = start + (numChars ? 36 : 26); + + if (whiteSpaces) + { + start--; + range++; + } + + QString output = ""; + for (int i = 0; i < length; i++) + output += alphaNumChars[rand(start, range)]; + + return output; +} + +QString randStr(int length, const QString& charCollection) +{ + int range = charCollection.size(); + QString output = ""; + for (int i = 0; i < length; i++) + output += charCollection[rand(0, range)]; + + return output; +} + +QString randBinStr(int length) +{ + char* output = new char[length]; + for (int i =0; i < length; i++) + output[i] = rand(0, 256); + + return QString::fromLatin1(output, length); +} + +QString randStrNotIn(int length, const QSet set, bool numChars, bool whiteSpaces) +{ + if (length == 0) + return ""; + + QString outStr; + do + { + outStr = randStr(length, numChars, whiteSpaces); + } + while (set.contains(outStr)); + + return outStr; +} + + +Range::Range() : + from(0), to(0) +{ +} + +Range::Range(qint64 from, qint64 to) + :from(from), to(to) +{ + fromValid = true; + toValid = true; +} + +void Range::setFrom(qint64 from) +{ + this->from = from; + fromValid = true; +} + +void Range::setTo(qint64 to) +{ + this->to = to; + toValid = true; +} + +qint64 Range::getFrom() const +{ + return from; +} + +qint64 Range::getTo() const +{ + return to; +} + +bool Range::isValid() const +{ + return fromValid && toValid && from <= to; +} + +bool Range::contains(qint64 position) const +{ + return position >= from && position <= to; +} + +bool Range::overlaps(const Range& other) const +{ + return overlaps(other.from, other.to); +} + +bool Range::overlaps(qint64 from, qint64 to) const +{ + return (this->from >= from && this->from <= to) || (this->to >= from && this->to <= to); +} + +Range Range::common(const Range& other) const +{ + return common(other.from, other.to); +} + +Range Range::common(qint64 from, qint64 to) const +{ + if (!isValid() || from > to) + return Range(); + + if (this->from >= from) + { + if (this->from > to) + return Range(); + + if (this->to < to) + return Range(this->from, this->to); + else + return Range(this->from, to); + } + else + { + if (from > this->to) + return Range(); + + if (to < this->to) + return Range(from, to); + else + return Range(from, this->to); + } +} + +qint64 Range::length() const +{ + return to - from + 1; +} + +QString generateUniqueName(const QString &baseName, const QStringList &existingNames) +{ + QString name = baseName; + int i = 0; + while (existingNames.contains(name)) + name = baseName+QString::number(i++); + + return name; +} + +bool isNumeric(const QVariant& value) +{ + bool ok; + value.toLongLong(&ok); + if (ok) + return true; + + value.toDouble(&ok); + return ok; +} + +QString rStrip(const QString& str) +{ + if (str.isNull()) + return str; + + for (int n = str.size() - 1; n >= 0; n--) + { + if (!str.at(n).isSpace()) + return str.left(n + 1); + } + return ""; +} + +QStringList tokenizeArgs(const QString& str) +{ + QStringList results; + QString token; + bool quote = false; + bool escape = false; + QChar c; + for (int i = 0; i < str.length(); i++) + { + c = str[i]; + if (escape) + { + token += c; + } + else if (c == '\\') + { + escape = true; + } + else if (quote) + { + if (c == '"') + { + results << token; + token.clear(); + quote = false; + } + else + { + token += c; + } + } + else if (c == '"') + { + if (token.length() > 0) + token += c; + else + quote = true; + } + else if (c.isSpace()) + { + if (token.length() > 0) + { + results << token; + token.clear(); + } + } + else + { + token += c; + } + } + + if (token.length() > 0) + results << token; + + return results; +} + +QStringList prefixEach(const QString& prefix, const QStringList& list) +{ + QStringList result; + foreach (const QString& item, list) + result << (prefix + item); + + return result; +} + +QByteArray hashToBytes(const QHash& hash) +{ + QByteArray bytes; + QDataStream stream(&bytes, QIODevice::WriteOnly); + stream << QVariant(hash); + return bytes; +} + +QHash bytesToHash(const QByteArray& bytes) +{ + if (bytes.isNull()) + return QHash(); + + QVariant deserializedValue; + QDataStream stream(bytes); + stream >> deserializedValue; + return deserializedValue.toHash(); +} + +int indexOf(const QStringList& list, const QString& value, Qt::CaseSensitivity cs) +{ + return indexOf(list, value, 0, cs); +} + +int indexOf(const QStringList& list, const QString& value, int from, Qt::CaseSensitivity cs) +{ + if (cs == Qt::CaseSensitive) + return list.indexOf(value, from); + + int cnt = list.size(); + for (int i = from; i < cnt; i++) + if (list[i].compare(value, cs) == 0) + return i; + + return -1; +} + +QString pad(const QString& str, int length, const QChar& fillChar) +{ + if (str.length() >= abs(length)) + return str; + + QString result = str; + QString fill = QString(fillChar).repeated(abs(length) - str.length()); + if (length >= 0) + return result.append(fill); + else + return result.prepend(fill); +} + +QString center(const QString& str, int length, const QChar& fillChar) +{ + if (str.length() >= length) + return str; + + QString result = str; + QString fillLeft = QString(fillChar).repeated((length - str.length()) / 2); + QString fillRight = fillLeft; + if ((fillLeft.length() + fillRight.length() + str.length()) < length) + fillLeft += fillChar; + + return result.prepend(fillLeft).append(fillRight); +} + +QString longest(const QStringList& strList) +{ + int max = 0; + QString result; + foreach (const QString str, strList) + { + if (str.size() > max) + { + result = str; + max = str.size(); + } + } + return result; +} + +QString shortest(const QStringList& strList) +{ + int max = INT_MAX; + QString result; + foreach (const QString str, strList) + { + if (str.size() < max) + { + result = str; + max = str.size(); + } + } + return result; +} + +QString longestCommonPart(const QStringList& strList) +{ + if (strList.size() == 0) + return QString::null; + + QString common; + QString first = strList.first(); + for (int i = 0; i < first.length(); i++) + { + common += first[i]; + foreach (const QString& str, strList) + { + if (!str.startsWith(common)) + return common.left(i); + } + } + return common; +} + +QStringList applyMargin(const QString& str, int margin) +{ + QStringList lines; + QString line; + foreach (QString word, str.split(" ")) + { + if (((line + word).length() + 1) > margin) + { + if (!line.isEmpty()) + { + lines << line; + line.clear(); + } + + while ((line + word).length() > margin) + { + line += word.left(margin); + lines << line; + word = word.mid(margin); + } + } + + if (!line.isEmpty()) + line += " "; + + line += word; + + if (line.endsWith("\n")) + { + lines << line.trimmed(); + line.clear(); + } + } + + if (!line.isEmpty()) + lines << line; + + if (lines.size() == 0) + lines << QString(); + + return lines; +} + +QDateTime toGregorian(double julianDateTime) +{ + int Z = (int)julianDateTime; + double F = julianDateTime - Z; + + int A; + if (Z < 2299161) + { + A = Z; + } + else + { + int alpha = (int)((Z - 1867216.25)/36524.25); + A = Z + 1 + alpha - (int)(alpha / 4); + } + + int B = A + 1524; + int C = (int)((B - 122.1) / 365.25); + int D = (int)(365.25 * C); + int E = (int)((B-D) / 30.6001); + int DD = B - D - (int)(30.6001 * E) + F; + int MM = (E <= 13) ? E - 1 : E - 13; + int YYYY = (MM <= 2) ? C - 4715 : C - 4716; + + int mmmBase = qRound(F * 86400000.0); + int mmm = mmmBase % 1000; + int ssBase = mmmBase / 1000; + int ss = ssBase % 60; + int mmBase = ssBase / 60; + int mm = mmBase % 60; + int hh = (mmBase / 60) + 12; + if (hh >= 24) + { + hh -= 24; + DD++; + } + + QDateTime dateTime; + dateTime.setDate(QDate(YYYY, MM, DD)); + dateTime.setTime(QTime(hh, mm, ss, mmm)); + return dateTime; +} + +double toJulian(const QDateTime& gregDateTime) +{ + QDate date = gregDateTime.date(); + QTime time = gregDateTime.time(); + return toJulian(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.msec()); +} + +double toJulian(int year, int month, int day, int hour, int minute, int second, int msec) +{ + int a = (14 - month) / 12; + int y = year + 4800 + a; + int m = month + 12 * a - 3; + + // Julian Day + int jnd = day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; + + // Julian Day + Julian Time + double jndt = jnd + (hour - 12.0) / 24.0 + minute / 1440.0 + second / 86400.0 + msec / 86400000.0; + + return jndt; +} + +QString formatFileSize(quint64 size) +{ + quint64 bytes = size; + quint64 kb = 0; + quint64 mb = 0; + quint64 gb = 0; + + QStringList words; + if (bytes > (1024*1024*1024)) + { + gb = bytes / (1024*1024*1024); + bytes %= (1024*1024*1024); + words << QString("%1GB").arg(gb); + } + + if (bytes > (1024*1024)) + { + mb = bytes / (1024*1024); + bytes %= (1024*1024); + words << QString("%1MB").arg(mb); + } + + if (bytes > 1024) + { + kb = bytes / 1024; + bytes %= 1024; + words << QString("%1KB").arg(kb); + } + + if (bytes > 0) + words << QString("%1B").arg(bytes); + + return words.join(" "); +} + +QString formatTimePeriod(int msecs) +{ + int hours = 0; + int minutes = 0; + int seconds = 0; + + QStringList words; + if (msecs > (1000*60*60)) + { + hours = msecs / (1000*60*60); + msecs %= (1000*60*60); + words << QString("%1h").arg(hours); + } + + if (msecs > (1000*60)) + { + minutes = msecs / (1000*60); + msecs %= (1000*60); + words << QString("%1m").arg(minutes); + } + + if (msecs > (1000)) + { + seconds = msecs / 1000; + msecs %= 1000; + words << QString("%1s").arg(seconds); + } + + if (msecs > 0) + words << QString("%1ms").arg(msecs); + + return words.join(" "); +} + +QStringList common(const QStringList& list1, const QStringList& list2, Qt::CaseSensitivity cs) +{ + QStringList newList; + for (const QString& str : list1) + { + if (list2.contains(str, cs)) + newList << str; + } + return newList; +} + +QStringList textCodecNames() +{ + QList codecs = QTextCodec::availableCodecs(); + QStringList names; + QSet nameSet; + for (const QByteArray& codec : codecs) + nameSet << QString::fromLatin1(codec.constData()); + + names = nameSet.toList(); + qSort(names); + return names; +} + +QTextCodec* codecForName(const QString& name) +{ + return QTextCodec::codecForName(name.toLatin1()); +} + +QTextCodec* defaultCodec() +{ + return QTextCodec::codecForLocale(); +} + +QString defaultCodecName() +{ + return QString::fromLatin1(QTextCodec::codecForLocale()->name()); +} + +QStringList splitByLines(const QString& str) +{ + return str.split(QRegExp("\r?\n")); +} + +QString joinLines(const QStringList& lines) +{ +#ifdef Q_OS_WIN + static_char* newLine = "\r\n"; +#else + static_char* newLine = "\n"; +#endif + return lines.join(newLine); +} + +int sum(const QList& integers) +{ + int res = 0; + for (int i : integers) + res += i; + + return res; +} + +QString getOsString() +{ +#if defined(Q_OS_WIN) + QString os = "Windows"; + switch (QSysInfo::WindowsVersion) + { + case QSysInfo::WV_XP: + os += " XP"; + break; + case QSysInfo::WV_2003: + os += " 2003"; + break; + case QSysInfo::WV_VISTA: + os += " Vista"; + break; + case QSysInfo::WV_WINDOWS7: + os += " 7"; + break; + case QSysInfo::WV_WINDOWS8: + os += " 8"; + break; + case QSysInfo::WV_WINDOWS8_1: + os += " 8.1"; + break; + case QSysInfo::WV_32s: + case QSysInfo::WV_95: + case QSysInfo::WV_98: + case QSysInfo::WV_Me: + case QSysInfo::WV_DOS_based: + case QSysInfo::WV_NT: + case QSysInfo::WV_2000: + case QSysInfo::WV_NT_based: + case QSysInfo::WV_CE: + case QSysInfo::WV_CENET: + case QSysInfo::WV_CE_5: + case QSysInfo::WV_CE_6: + case QSysInfo::WV_CE_based: + break; + } +#elif defined(Q_OS_LINUX) + QString os = "Linux"; + utsname uts; + if (uname(&uts) != 0) + { + qWarning() << "Error while calling uname() for OS version. Error code: " << errno; + } + else + { + os += " " + QString::fromLatin1(uts.release); + } +#elif defined(Q_OS_OSX) + QString os = "MacOS X"; + switch (QSysInfo::MacintoshVersion) + { + case QSysInfo::MV_10_4: + os += " 10.4 Tiger"; + break; + case QSysInfo::MV_10_5: + os += " 10.5 Leopard"; + break; + case QSysInfo::MV_10_6: + os += " 10.6 Snow Leopard"; + break; + case QSysInfo::MV_10_7: + os += " 10.7 Lion"; + break; + case QSysInfo::MV_10_8: + os += " 10.8 Mountain Lion"; + break; + case QSysInfo::MV_10_9: + os += " 10.9 Mavericks"; + break; + case QSysInfo::MV_9: + case QSysInfo::MV_10_0: + case QSysInfo::MV_10_1: + case QSysInfo::MV_10_2: + case QSysInfo::MV_10_3: + case QSysInfo::MV_IOS: + case QSysInfo::MV_IOS_4_3: + case QSysInfo::MV_IOS_5_0: + case QSysInfo::MV_IOS_5_1: + case QSysInfo::MV_IOS_6_0: + case QSysInfo::MV_IOS_6_1: + case QSysInfo::MV_IOS_7_0: + case QSysInfo::MV_IOS_7_1: + case QSysInfo::MV_Unknown: + break; + } +#elif defined(Q_OS_UNIX) + QString os = "Unix"; +#else + QString os = "Unknown"; +#endif + + os += ", " + QString::number(QSysInfo::WordSize) + "bit"; + return os; +} + +DistributionType getDistributionType() +{ +#if defined(Q_OS_OSX) + return DistributionType::OSX_BOUNDLE; +#elif defined(PORTABLE_CONFIG) + return DistributionType::PORTABLE; +#else + return DistributionType::OS_MANAGED; +#endif +} + +bool validateEmail(const QString& email) +{ + static const QRegularExpression re("^[a-zA-Z0-9_\\.-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-\\.]+$"); + return re.match(email).hasMatch(); +} + +bool isHex(const QString& str) +{ + bool ok; + str.toLongLong(&ok, 16); + return ok; +} + +QString formatVersion(int version) +{ + int majorVer = version / 10000; + int minorVer = version % 10000 / 100; + int patchVer = version % 100; + return QString::number(majorVer) + "." + QString::number(minorVer) + "." + QString::number(patchVer); +} + +bool copyRecursively(const QString& src, const QString& dst) +{ + // Code taken from QtCreator: + // https://qt.gitorious.org/qt-creator/qt-creator/source/1a37da73abb60ad06b7e33983ca51b266be5910e:src/app/main.cpp#L13-189 + QFileInfo srcFileInfo(src); + if (srcFileInfo.isDir()) + { + QDir targetDir(dst); + targetDir.cdUp(); + if (!targetDir.mkdir(QFileInfo(dst).fileName())) + return false; + + QDir sourceDir(src); + QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + for (const QString &fileName : fileNames) + { + const QString newSrcFilePath = src + QLatin1Char('/') + fileName; + const QString newTgtFilePath = dst + QLatin1Char('/') + fileName; + if (!copyRecursively(newSrcFilePath, newTgtFilePath)) + return false; + } + } + else if (srcFileInfo.isSymLink()) + { + QString trg = QFile(src).symLinkTarget(); + QFile::link(trg, dst); + } + else + { + if (!QFile::copy(src, dst)) + return false; + } + return true; +} + +bool renameBetweenPartitions(const QString& src, const QString& dst) +{ + if (QDir(dst).exists()) + return false; + + int res = copyRecursively(src, dst); + if (res) + QDir(src).removeRecursively(); + else + QDir(dst).removeRecursively(); + + return res; +} + +bool isWritableRecursively(const QString& dir) +{ + QFileInfo fi(dir); + if (!fi.isWritable()) + return false; + + if (fi.isDir()) + { + QStringList fileNames = QDir(dir).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + for (const QString &fileName : fileNames) + { + if (!isWritableRecursively(dir + QLatin1Char('/') + fileName)) + return false; + } + } + return true; +} + +QString encryptRsa(const QString& input, const QString& modulus, const QString& exponent) +{ + std::string inputStdStr = input.toStdString(); + Key key = Key(BigInt(modulus.toStdString()), BigInt(exponent.toStdString())); + std::string result = RSA::Encrypt(inputStdStr, key); + return QString::fromStdString(result); +} + +QString decryptRsa(const QString& input, const QString& modulus, const QString& exponent) +{ + std::string inputStdStr = input.toStdString(); + Key key = Key(BigInt(modulus.toStdString()), BigInt(exponent.toStdString())); + std::string result = RSA::Decrypt(inputStdStr, key); + return QString::fromStdString(result); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils.h b/SQLiteStudio3/coreSQLiteStudio/common/utils.h new file mode 100644 index 0000000..4ded52b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/utils.h @@ -0,0 +1,244 @@ +#ifndef UTILS_H +#define UTILS_H + +#include "coreSQLiteStudio_global.h" +#include +#include +#include +#include +#include +#include + +class QTextCodec; + +API_EXPORT void initUtils(); + +class API_EXPORT Range +{ + public: + Range(); + Range(qint64 from, qint64 to); + + void setFrom(qint64 from); + void setTo(qint64 to); + qint64 getFrom() const; + qint64 getTo() const; + bool isValid() const; + bool contains(qint64 position) const; + bool overlaps(const Range& other) const; + bool overlaps(qint64 from, qint64 to) const; + Range common(const Range& other) const; + Range common(qint64 from, qint64 to) const; + qint64 length() const; + + private: + qint64 from; + qint64 to; + bool fromValid = false; + bool toValid = false; +}; + +API_EXPORT bool isXDigit(const QChar& c); + +/** + * @brief Get character from string. + * @param str String to get char from. + * @param pos Character position. + * @return Requested character or null character. + * + * This is safe getter for a character of the string, + * thish returns null if pos index is out of range. + */ +QChar API_EXPORT charAt(const QString& str, int pos); + +API_EXPORT int rand(int min = 0, int max = RAND_MAX); +API_EXPORT QString randStr(int length, bool numChars = true, bool whiteSpaces = false); +API_EXPORT QString randStr(int length, const QString& charCollection); +API_EXPORT QString randBinStr(int length); +API_EXPORT QString randStrNotIn(int length, const QSet set, bool numChars = true, bool whiteSpaces = false); +API_EXPORT QString generateUniqueName(const QString& prefix, const QStringList& existingNames); +API_EXPORT bool isNumeric(const QVariant& value); +API_EXPORT QString rStrip(const QString& str); +API_EXPORT QStringList tokenizeArgs(const QString& str); +API_EXPORT QStringList prefixEach(const QString& prefix, const QStringList& list); +API_EXPORT QByteArray hashToBytes(const QHash& hash); +API_EXPORT QHash bytesToHash(const QByteArray& bytes); +/** + * @brief indexOf Extension to QStringList::indexOf(). + * + * This method does pretty much the same as QStringList::indexOf(), except it supports + * case sensitivity flag, unlike the original method. + */ +API_EXPORT int indexOf(const QStringList& list, const QString& value, int from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive); +API_EXPORT int indexOf(const QStringList& list, const QString& value, Qt::CaseSensitivity cs = Qt::CaseSensitive); + +/** + * @brief Returns only those elements from the list, which passed the filter. + * @tparam T type for which the filter will be applied for. It should match the type in the list and in the function argument. + * @param list List to filter elements from. + * @param filterFunction Function that accepts elements from the list and returns true for elements that should be returned by the filter. + * @return List of elements that passed custom function validation. + */ +template +QList filter(const QList& list, std::function filterFunction) +{ + QList results; + for (const T& value : list) + { + if (filterFunction(value)) + results << value; + } + return results; +} + +template +bool contains(const QList& list, std::function testFunction) +{ + for (const T& value : list) + { + if (testFunction(value)) + return true; + } + return false; +} + +/** + * @brief Appends or prepends characters to the string to make it of specified length. + * @param str Input string to work with. + * @param length Desired length of output string. + * @param fillChar Character to use to fill missing part of string. + * @param String which is at least \p length characters long, using \p str as an initial value. + * + * It appends or prepends as many \p fillChar characters to the \p str, so the \p str becomes \p length characters long. + * In case the \p str is already \p length characters long, or even longer, then the original string is returned. + * + * If \p length is positive value, characters are appended to string. It it's negative, then values are prepended to the string, + * using an absolute value of the \p length for calculating output length. + */ +API_EXPORT QString pad(const QString& str, int length, const QChar& fillChar); + +API_EXPORT QString center(const QString& str, int length, const QChar& fillChar); + +/** + * @brief Picks the longest string from the list. + * @param strList List to pick from. + * @return Longest value from the list, or empty string if the \p strList was empty as well. + * + * If there are many values with the same, longest length, then the first one is picked. + */ +API_EXPORT QString longest(const QStringList& strList); + +/** + * @brief Picks the shortest string from the list. + * @param strList List to pick from. + * @return Shortest value from the list, or empty string if the \p strList was empty as well. + * + * If there are many values with the same, shortest length, then the first one is picked. + */ +API_EXPORT QString shortest(const QStringList& strList); + +/** + * @brief Finds the longest common part of all strings. + * @param strList List to compare strings from. + * @return Longest common string (looking from the begining of each string) from the list. + */ +API_EXPORT QString longestCommonPart(const QStringList& strList); + +/** + * @brief Applies margin of given number of characters to the string, splitting it into lines. + * @param str String to apply the margin to. + * @param margin Number of characters allows in single line. + * @return List of lines produced by applying the margin. + * + * If \p str is longer than \p margin number of characters, this method splits \p str into several lines + * in order to respect the \p margin. White spaces are taken as points of splitting, but if there is + * a single word longer than \p margin, then this word gets splitted. + */ +API_EXPORT QStringList applyMargin(const QString& str, int margin); + +/** + * @brief toGregorian Converts Julian Date to Gregorian Date. + * @param julianDateTime Floating point representing Julian Day and Julian time. + * @return Gregorian date. + * + * Converts Julian Calendar date into Gregorian Calendar date. + * See Wikipedia for details. + */ +API_EXPORT QDateTime toGregorian(double julianDateTime); + +/** + * @brief toJulian Converts Gregorian Date to Julian Date. + * @param gregDateTime Gregorian calendar date and time. + * @return Julian calendar date and time. + * + * Converts the usually used Gregorian date and time into Julian Date format, + * which is floating point, where integral part is the Julian Day and fraction part is time of the day. + */ +API_EXPORT double toJulian(const QDateTime& gregDateTime); + +/** + * @brief toJulian Converts Gregorian Date to Julian Date. + * @overload + */ +API_EXPORT double toJulian(int year, int month, int day, int hour, int minute, int second, int msec); + +API_EXPORT QString formatFileSize(quint64 size); + +API_EXPORT QString formatTimePeriod(int msecs); + +API_EXPORT QStringList common(const QStringList& list1, const QStringList& list2, Qt::CaseSensitivity cs = Qt::CaseSensitive); + +API_EXPORT QStringList textCodecNames(); +API_EXPORT QString defaultCodecName(); +API_EXPORT QTextCodec* defaultCodec(); +API_EXPORT QTextCodec* codecForName(const QString& name); +API_EXPORT QStringList splitByLines(const QString& str); +API_EXPORT QString joinLines(const QStringList& lines); +API_EXPORT int sum(const QList& integers); +API_EXPORT QString getOsString(); +API_EXPORT bool validateEmail(const QString& email); +API_EXPORT bool isHex(const QString& str); +API_EXPORT QString formatVersion(int version); +API_EXPORT bool copyRecursively(const QString& src, const QString& dst); +API_EXPORT bool renameBetweenPartitions(const QString& src, const QString& dst); +API_EXPORT bool isWritableRecursively(const QString& dir); +API_EXPORT QString encryptRsa(const QString& input, const QString& modulus, const QString& exponent); +API_EXPORT QString decryptRsa(const QString& input, const QString& modulus, const QString& exponent); + +enum class DistributionType +{ + PORTABLE, + OSX_BOUNDLE, + OS_MANAGED +}; + +API_EXPORT DistributionType getDistributionType(); + +template +QList reverse(const QList& list) +{ + QList result; + for (const T& el : list) + result.prepend(el); + + return result; +} + +template +void removeDuplicates(QList& list) +{ + QSet set; + QMutableListIterator i(list); + while (i.hasNext()) + { + i.next(); + if (set.contains(i.value())) + i.remove(); + else + set << i.value(); + } +} + +Q_DECLARE_METATYPE(QList) + +#endif // UTILS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp new file mode 100644 index 0000000..fcf49af --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp @@ -0,0 +1,552 @@ +#include "common/utils_sql.h" +#include "common/utils.h" +#include "db/sqlquery.h" +#include "parser/token.h" +#include "parser/lexer.h" +#include "parser/keywords.h" +#include +#include +#include +#include +#include + +QString invalidIdCharacters = "[]()$\"'@*.,+-=/%&|:; \t\n<>"; +QHash > wrapperChars; +QList sqlite3Wrappers; +QList sqlite2Wrappers; + +void initUtilsSql() +{ + wrapperChars[NameWrapper::BRACKET] = QPair('[', ']'); + wrapperChars[NameWrapper::QUOTE] = QPair('\'', '\''); + wrapperChars[NameWrapper::BACK_QUOTE] = QPair('`', '`'); + wrapperChars[NameWrapper::DOUBLE_QUOTE] = QPair('"', '"'); + + sqlite3Wrappers << NameWrapper::DOUBLE_QUOTE + << NameWrapper::BRACKET + << NameWrapper::QUOTE + << NameWrapper::BACK_QUOTE; + sqlite2Wrappers << NameWrapper::DOUBLE_QUOTE + << NameWrapper::BRACKET + << NameWrapper::QUOTE; + + qRegisterMetaType("SqlQueryPtr"); +} + +bool doesObjectNeedWrapping(const QString& str, Dialect dialect) +{ + if (str.isEmpty()) + return true; + + if (isObjWrapped(str, dialect)) + return false; + + if (isKeyword(str, dialect)) + return true; + + for (int i = 0; i < str.size(); i++) + if (doesObjectNeedWrapping(str[i])) + return true; + + if (str[0].isDigit()) + return true; + + return false; +} + +bool doesObjectNeedWrapping(const QChar& c) +{ + return invalidIdCharacters.indexOf(c) >= 0; +} + +bool isObjectWrapped(const QChar& c, Dialect dialect) +{ + return !doesObjectNeedWrapping(c, dialect); +} + +bool isObjectWrapped(const QChar& c) +{ + return !doesObjectNeedWrapping(c); +} + +QString wrapObjIfNeeded(const QString& obj, Dialect dialect, NameWrapper favWrapper) +{ + if (doesObjectNeedWrapping(obj, dialect)) + return wrapObjName(obj, dialect, favWrapper); + + return obj; +} + +QString wrapObjName(const QString& obj, Dialect dialect, NameWrapper favWrapper) +{ + QString result = obj; + if (result.isNull()) + result = ""; + + QPair wrapChars = getQuoteCharacter(result, dialect, favWrapper); + + if (wrapChars.first.isNull() || wrapChars.second.isNull()) + { + qDebug() << "No quote character possible for object name: " << result; + return result; + } + result.prepend(wrapChars.first); + result.append(wrapChars.second); + return result; +} + +QString wrapObjName(const QString& obj, NameWrapper wrapper) +{ + QString result = obj; + if (wrapper == NameWrapper::null) + return result; + + result.prepend(wrapperChars[wrapper].first); + result.append(wrapperChars[wrapper].second); + return result; +} + +QPair getQuoteCharacter(QString& obj, Dialect dialect, NameWrapper favWrapper) +{ + QList wrappers = (dialect == Dialect::Sqlite3) ? sqlite3Wrappers : sqlite2Wrappers; + + // Move favourite wrapper to front of list + if (wrappers.contains(favWrapper)) + { + wrappers.removeOne(favWrapper); + wrappers.insert(0, favWrapper); + } + + QPair wrapChars; + foreach (NameWrapper wrapper, wrappers) + { + wrapChars = wrapperChars[wrapper]; + if (obj.indexOf(wrapChars.first) > -1) + continue; + + if (obj.indexOf(wrapChars.second) > -1) + continue; + + return wrapChars; + } + + return QPair(); +} + +QList wrapObjNames(const QList& objList, Dialect dialect, NameWrapper favWrapper) +{ + QList results; + for (int i = 0; i < objList.size(); i++) + results << wrapObjName(objList[i], dialect, favWrapper); + + return results; +} + +QList wrapObjNamesIfNeeded(const QList& objList, Dialect dialect, NameWrapper favWrapper) +{ + QList results; + for (int i = 0; i < objList.size(); i++) + results << wrapObjIfNeeded(objList[i], dialect, favWrapper); + + return results; +} + +QList getAllNameWrappers(Dialect dialect) +{ + if (dialect == Dialect::Sqlite3) + return {NameWrapper::DOUBLE_QUOTE, NameWrapper::BRACKET, NameWrapper::BACK_QUOTE, NameWrapper::QUOTE}; + else + return {NameWrapper::DOUBLE_QUOTE, NameWrapper::BRACKET, NameWrapper::QUOTE}; +} + +QString wrapString(const QString& str) +{ + QString result = str; + result.prepend("'"); + result.append("'"); + return result; +} + +bool doesStringNeedWrapping(const QString& str) +{ + return str[0] == '\'' && str[str.length()-1] == '\''; +} + +bool isStringWrapped(const QString& str) +{ + return !doesStringNeedWrapping(str); +} + +QString wrapStringIfNeeded(const QString& str) +{ + if (isStringWrapped(str)) + return wrapString(str); + + return str; +} + +QString escapeString(QString& str) +{ + return str.replace('\'', "''"); +} + +QString escapeString(const QString& str) +{ + QString newStr = str; + return newStr.replace('\'', "''"); +} + +QString stripString(QString& str) +{ + if (str.length() <= 1) + return str; + + if (str[0] == '\'' && str[str.length()-1] == '\'') + return str.mid(1, str.length()-2); + + return str; +} + +QString stripString(const QString& str) +{ + QString newStr = str; + return stripString(newStr); +} + +QString stripEndingSemicolon(const QString& str) +{ + QString newStr = rStrip(str); + if (newStr.size() == 0) + return str; + + if (newStr[newStr.size()-1] == ';') + { + newStr.chop(1); + return newStr; + } + else + return str; +} + +QString stripObjName(const QString &str, Dialect dialect) +{ + QString newStr = str; + return stripObjName(newStr, dialect); +} + +QString stripObjName(QString &str, Dialect dialect) +{ + if (str.isNull()) + return str; + + if (str.length() <= 1) + return str; + + if (!isObjWrapped(str, dialect)) + return str; + + return str.mid(1, str.length()-2); +} + +bool isObjWrapped(const QString& str, Dialect dialect) +{ + return getObjWrapper(str, dialect) != NameWrapper::null; +} + +NameWrapper getObjWrapper(const QString& str, Dialect dialect) +{ + if (str.isEmpty()) + return NameWrapper::null; + + QList wrappers; + + if (dialect == Dialect::Sqlite2) + wrappers = sqlite2Wrappers; + else + wrappers = sqlite3Wrappers; + + foreach (NameWrapper wrapper, wrappers) + { + QPair chars = wrapperChars[wrapper]; + if (str[0] == chars.first && str[str.length()-1] == chars.second) + return wrapper; + } + return NameWrapper::null; +} + +bool isWrapperChar(const QChar& c, Dialect dialect) +{ + QList wrappers; + if (dialect == Dialect::Sqlite2) + wrappers = sqlite2Wrappers; + else + wrappers = sqlite3Wrappers; + + foreach (NameWrapper wrapper, wrappers) + { + QPair chars = wrapperChars[wrapper]; + if (c == chars.first || c == chars.second) + return true; + } + return false; +} + +int qHash(NameWrapper wrapper) +{ + return (uint)wrapper; +} + +QString getPrefixDb(const QString& origDbName, Dialect dialect) +{ + if (origDbName.isEmpty()) + return "main"; + else + return wrapObjIfNeeded(origDbName, dialect); +} + +bool isSystemTable(const QString &name) +{ + return name.startsWith("sqlite_"); +} + +bool isSystemIndex(const QString &name, Dialect dialect) +{ + switch (dialect) + { + case Dialect::Sqlite3: + return name.startsWith("sqlite_autoindex_"); + case Dialect::Sqlite2: + { + QRegExp re("*(*autoindex*)*"); + re.setPatternSyntax(QRegExp::Wildcard); + return re.exactMatch(name); + } + } + return false; +} + + +TokenPtr stripObjName(TokenPtr token, Dialect dialect) +{ + if (!token) + return token; + + token->value = stripObjName(token->value, dialect); + return token; +} + +QString removeComments(const QString& value) +{ + Lexer lexer(Dialect::Sqlite3); + TokenList tokens = lexer.tokenize(value); + while (tokens.remove(Token::COMMENT)) + continue; + + return tokens.detokenize(); +} + +QList splitQueries(const TokenList& tokenizedQuery, bool* complete) +{ + QList queries; + TokenList currentQueryTokens; + QString value; + int createTriggerMeter = 0; + bool insideTrigger = false; + bool completeQuery = false; + foreach (const TokenPtr& token, tokenizedQuery) + { + value = token->value.toUpper(); + if (!token->isWhitespace()) + completeQuery = false; + + if (insideTrigger) + { + if (token->type == Token::KEYWORD && value == "END") + { + insideTrigger = false; + completeQuery = true; + } + + currentQueryTokens << token; + continue; + } + + if (token->type == Token::KEYWORD) + { + if (value == "CREATE" || value == "TRIGGER" || value == "BEGIN") + createTriggerMeter++; + + if (createTriggerMeter == 3) + insideTrigger = true; + + currentQueryTokens << token; + } + else if (token->type == Token::OPERATOR && value == ";") + { + createTriggerMeter = 0; + currentQueryTokens << token; + queries << currentQueryTokens; + currentQueryTokens.clear(); + completeQuery = true; + } + else + { + currentQueryTokens << token; + } + } + + if (currentQueryTokens.size() > 0) + queries << currentQueryTokens; + + if (complete) + *complete = completeQuery; + + return queries; +} + +QStringList splitQueries(const QString& sql, Dialect dialect, bool keepEmptyQueries, bool* complete) +{ + TokenList tokens = Lexer::tokenize(sql, dialect); + QList tokenizedQueries = splitQueries(tokens, complete); + + QString query; + QStringList queries; + foreach (const TokenList& queryTokens, tokenizedQueries) + { + query = queryTokens.detokenize(); + if (keepEmptyQueries || !query.trimmed().isEmpty()) + queries << query; + } + + return queries; +} + +QString getQueryWithPosition(const QStringList& queries, int position, int* startPos) +{ + int currentPos = 0; + int length = 0; + + if (startPos) + *startPos = 0; + + foreach (const QString& query, queries) + { + length = query.length(); + if (position >= currentPos && position < currentPos+length) + return query; + + currentPos += length; + + if (startPos) + *startPos += length; + } + + // If we passed all queries and it happens that the cursor is just after last query - this is the query we want. + if (position == currentPos && queries.size() > 0) + { + if (startPos) + *startPos -= length; + + return queries.last(); + } + + if (startPos) + *startPos = -1; + + return QString::null; +} + +QString getQueryWithPosition(const QString& queries, int position, Dialect dialect, int* startPos) +{ + QStringList queryList = splitQueries(queries, dialect); + return getQueryWithPosition(queryList, position, startPos); +} + +QString trimBindParamPrefix(const QString& param) +{ + if (param == "?") + return param; + + if (param.startsWith("$") || param.startsWith("@") || param.startsWith(":") || param.startsWith("?")) + return param.mid(1); + + return param; +} + +QList getQueriesWithParamNames(const QString& query, Dialect dialect) +{ + QList results; + + TokenList allTokens = Lexer::tokenize(query, dialect); + QList queries = splitQueries(allTokens); + + QString queryStr; + QStringList paramNames; + foreach (const TokenList& tokens, queries) + { + paramNames.clear(); + foreach (const TokenPtr& token, tokens.filter(Token::BIND_PARAM)) + paramNames << token->value; + + queryStr = tokens.detokenize().trimmed(); + if (!queryStr.isEmpty()) + results << QueryWithParamNames(queryStr, paramNames); + } + return results; +} + +QList getQueriesWithParamCount(const QString& query, Dialect dialect) +{ + QList results; + + TokenList allTokens = Lexer::tokenize(query, dialect); + QList queries = splitQueries(allTokens); + + QString queryStr; + foreach (const TokenList& tokens, queries) + { + queryStr = tokens.detokenize().trimmed(); + if (!queryStr.isEmpty()) + results << QueryWithParamCount(queryStr, tokens.filter(Token::BIND_PARAM).size()); + } + + return results; +} + +QueryWithParamNames getQueryWithParamNames(const QString& query, Dialect dialect) +{ + TokenList allTokens = Lexer::tokenize(query, dialect); + + QStringList paramNames; + foreach (const TokenPtr& token, allTokens.filter(Token::BIND_PARAM)) + paramNames << token->value; + + return QueryWithParamNames(query, paramNames); +} + +QueryWithParamCount getQueryWithParamCount(const QString& query, Dialect dialect) +{ + TokenList allTokens = Lexer::tokenize(query, dialect); + return QueryWithParamCount(query, allTokens.filter(Token::BIND_PARAM).size()); +} + +QString commentAllSqlLines(const QString& sql) +{ + QStringList lines = splitByLines(sql); + QMutableStringListIterator it(lines); + while (it.hasNext()) + it.next().prepend("-- "); + + return joinLines(lines); +} + +QString getBindTokenName(const TokenPtr& token) +{ + if (token->type != Token::BIND_PARAM) + return QString(); + + if (token->value == "?") + return token->value; + + return token->value.mid(1); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h new file mode 100644 index 0000000..8e1f3ff --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h @@ -0,0 +1,71 @@ +#ifndef UTILS_SQL_H +#define UTILS_SQL_H + +#include "dialect.h" +#include "parser/token.h" +#include "coreSQLiteStudio_global.h" +#include +#include +#include + +// TODO: unit tests for most of methods from this module + +enum class NameWrapper +{ + DOUBLE_QUOTE, + QUOTE, + BACK_QUOTE, + BRACKET, + null +}; + +typedef QPair QueryWithParamNames; +typedef QPair QueryWithParamCount; + +API_EXPORT void initUtilsSql(); +API_EXPORT bool doesObjectNeedWrapping(const QString& str, Dialect dialect); +API_EXPORT bool doesObjectNeedWrapping(const QChar& c); +API_EXPORT bool isObjectWrapped(const QChar& c, Dialect dialect); +API_EXPORT bool isObjectWrapped(const QChar& c); +API_EXPORT bool doesStringNeedWrapping(const QString& str); +API_EXPORT bool isStringWrapped(const QString& str); +API_EXPORT QString wrapObjIfNeeded(const QString& obj, Dialect dialect, NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QString wrapObjName(const QString& obj, Dialect dialect, NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QString wrapObjName(const QString& obj, NameWrapper wrapper); +API_EXPORT TokenPtr stripObjName(TokenPtr token, Dialect dialect); +API_EXPORT QString stripObjName(const QString &str, Dialect dialect); +API_EXPORT QString stripObjName(QString& str, Dialect dialect); +API_EXPORT bool isObjWrapped(const QString& str, Dialect dialect); +API_EXPORT NameWrapper getObjWrapper(const QString& str, Dialect dialect); +API_EXPORT bool isWrapperChar(const QChar& c, Dialect dialect); +API_EXPORT QString wrapString(const QString& str); +API_EXPORT QString wrapStringIfNeeded(const QString& str); +API_EXPORT QString escapeString(QString &str); +API_EXPORT QString escapeString(const QString& str); +API_EXPORT QString stripString(QString& str); +API_EXPORT QString stripString(const QString& str); +API_EXPORT QString stripEndingSemicolon(const QString& str); +API_EXPORT QPair getQuoteCharacter(QString& obj, Dialect dialect, + NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QList wrapObjNames(const QList& objList, Dialect dialect = Dialect::Sqlite3, NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QList wrapObjNamesIfNeeded(const QList& objList, Dialect dialect, NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QList getAllNameWrappers(Dialect dialect = Dialect::Sqlite3); +API_EXPORT int qHash(NameWrapper wrapper); +API_EXPORT QString getPrefixDb(const QString& origDbName, Dialect dialect); +API_EXPORT bool isSystemTable(const QString& name); +API_EXPORT bool isSystemIndex(const QString& name, Dialect dialect); +API_EXPORT QString removeComments(const QString& value); +API_EXPORT QList splitQueries(const TokenList& tokenizedQueries, bool* complete = nullptr); +API_EXPORT QStringList splitQueries(const QString& sql, Dialect dialect, bool keepEmptyQueries = true, bool* complete = nullptr); +API_EXPORT QString getQueryWithPosition(const QStringList& queries, int position, int* startPos = nullptr); +API_EXPORT QString getQueryWithPosition(const QString& queries, int position, Dialect dialect, int* startPos = nullptr); +API_EXPORT QList getQueriesWithParamNames(const QString& query, Dialect dialect); +API_EXPORT QList getQueriesWithParamCount(const QString& query, Dialect dialect); +API_EXPORT QueryWithParamNames getQueryWithParamNames(const QString& query, Dialect dialect); +API_EXPORT QueryWithParamCount getQueryWithParamCount(const QString& query, Dialect dialect); +API_EXPORT QString trimBindParamPrefix(const QString& param); +API_EXPORT QString commentAllSqlLines(const QString& sql); +API_EXPORT QString getBindTokenName(const TokenPtr& token); + + +#endif // UTILS_SQL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp new file mode 100644 index 0000000..978395f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp @@ -0,0 +1,439 @@ +#include "completioncomparer.h" +#include "completionhelper.h" +#include "parser/ast/sqliteselect.h" +#include "db/db.h" +#include "parser/token.h" +#include + +CompletionComparer::CompletionComparer(CompletionHelper *helper) + : helper(helper) +{ + dialect = helper->db->getDialect(); + init(); +} + +bool CompletionComparer::operator ()(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + if ((token1->priority > 0 || token2->priority > 0) && token1->priority != token2->priority) + return token1->priority > token2->priority; + + if (token1->type != token2->type) + return token1->type < token2->type; + + switch (token1->type) + { + case ExpectedToken::COLUMN: + return compareColumns(token1, token2); + case ExpectedToken::TABLE: + return compareTables(token1, token2); + case ExpectedToken::INDEX: + return compareIndexes(token1, token2); + case ExpectedToken::TRIGGER: + return compareTriggers(token1, token2); + case ExpectedToken::VIEW: + return compareViews(token1, token2); + case ExpectedToken::DATABASE: + return compareDatabases(token1, token2); + case ExpectedToken::KEYWORD: + case ExpectedToken::FUNCTION: + case ExpectedToken::OPERATOR: + case ExpectedToken::PRAGMA: + return compareValues(token1, token2); + case ExpectedToken::COLLATION: + { + if (dialect == Dialect::Sqlite3) + return compareValues(token1, token2); + else + return false; + } + case ExpectedToken::OTHER: + case ExpectedToken::STRING: + case ExpectedToken::NUMBER: + case ExpectedToken::BLOB: + case ExpectedToken::NO_VALUE: + return false; + } + + return false; +} + +void CompletionComparer::init() +{ + if (helper->originalParsedQuery) + { + bool contextObjectsInitialized = false; + if (helper->originalParsedQuery->queryType == SqliteQueryType::Select) + contextObjectsInitialized = initSelect(); + + if (!contextObjectsInitialized) + { + contextColumns = helper->originalParsedQuery->getContextColumns(); + contextTables = helper->originalParsedQuery->getContextTables(); + contextDatabases = helper->originalParsedQuery->getContextDatabases(false); + } + + foreach (SelectResolver::Table table, helper->selectAvailableTables + helper->parentSelectAvailableTables) + availableTableNames += table.table; + } +} + +bool CompletionComparer::initSelect() +{ + if (!helper->originalCurrentSelectCore) + return false; + + // This is similar to what is done in init() itself, except here it's limited + // to the current select core, excluding parent statement. + contextColumns = helper->originalCurrentSelectCore->getContextColumns(false); + contextTables = helper->originalCurrentSelectCore->getContextTables(false); + contextDatabases = helper->originalCurrentSelectCore->getContextDatabases(false); + + foreach (SqliteSelect::Core* core, helper->parentSelectCores) + { + parentContextColumns += core->getContextColumns(false); + parentContextTables += core->getContextTables(false); + parentContextDatabases += core->getContextDatabases(false); + } + + if (helper->context == CompletionHelper::Context::SELECT_RESULT_COLUMN) + { + // Getting list of result columns already being selected in the query + resultColumns = helper->selectResolver->resolve(helper->currentSelectCore); + } + + return true; +} + +bool CompletionComparer::compareColumns(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + if (!helper->parsedQuery) + return compareValues(token1, token2); + + bool ok = false; + bool result = true; + switch (helper->context) + { + case CompletionHelper::Context::SELECT_WHERE: + case CompletionHelper::Context::SELECT_GROUP_BY: + case CompletionHelper::Context::SELECT_HAVING: + case CompletionHelper::Context::SELECT_RESULT_COLUMN: + case CompletionHelper::Context::SELECT_ORDER_BY: + result = compareColumnsForSelectResCol(token1, token2, &ok); + break; + case CompletionHelper::Context::UPDATE_COLUMN: + case CompletionHelper::Context::UPDATE_WHERE: + result = compareColumnsForUpdateCol(token1, token2, &ok); + break; + case CompletionHelper::Context::DELETE_WHERE: + result = compareColumnsForDeleteCol(token1, token2, &ok); + break; + case CompletionHelper::Context::CREATE_TABLE: + result = compareColumnsForCreateTable(token1, token2, &ok); + break; + default: + return compareValues(token1, token2); + } + + if (ok) + return result; + + result = compareByContext(token1->value, token2->value, {contextColumns, parentContextColumns}, true, &ok); + if (ok) + return result; + + // Context info of the column has a strong meaning when sorting (system tables are pushed to the end) + bool firstIsSystem = token1->contextInfo.toLower().startsWith("sqlite_"); + bool secondIsSystem = token2->contextInfo.toLower().startsWith("sqlite_"); + if (firstIsSystem && !secondIsSystem) + return false; + + if (!firstIsSystem && secondIsSystem) + return true; + + return compareValues(token1->value, token2->value, true); +} + +bool CompletionComparer::compareColumnsForSelectResCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result) +{ + *result = true; + + // Checking if columns are on list of columns available in FROM clause + bool token1available = isTokenOnAvailableList(token1); + bool token2available = isTokenOnAvailableList(token2); + if (token1available && !token2available) + return true; + + if (!token1available && token2available) + return false; + + // Checking if columns are on list of columns available in FROM clause of any parent SELECT core + bool token1parentAvailable = isTokenOnParentAvailableList(token1); + bool token2parentAvailable = isTokenOnParentAvailableList(token2); + if (token1parentAvailable && !token2parentAvailable) + return true; + + if (!token1parentAvailable && token2parentAvailable) + return false; + + // Checking if columns were already mentioned in results list. + // It it was, it should be pushed back. + bool token1onResCols = isTokenOnResultColumns(token1); + bool token2onResCols = isTokenOnResultColumns(token2); + if (token1onResCols && !token2onResCols) + return false; + + if (!token1onResCols && token2onResCols) + return true; + + *result = false; + return false; +} + +bool CompletionComparer::compareColumnsForUpdateCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result) +{ + *result = true; + if (token1->contextInfo == token2->contextInfo) + return compareValues(token1->value, token2->value); + + return compareByContext(token1->contextInfo, token2->contextInfo, contextTables); +} + +bool CompletionComparer::compareColumnsForDeleteCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result) +{ + *result = true; + if (token1->contextInfo == token2->contextInfo) + return compareValues(token1->value, token2->value); + + return compareByContext(token1->contextInfo, token2->contextInfo, contextTables); +} + +bool CompletionComparer::compareColumnsForCreateTable(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result) +{ + *result = true; + + bool token1OnAvailableList = helper->favoredColumnNames.contains(token1->value) && contextTables.contains(token1->contextInfo); + bool token2OnAvailableList = helper->favoredColumnNames.contains(token2->value) && contextTables.contains(token2->contextInfo); + if (token1OnAvailableList && !token2OnAvailableList) + return true; + + if (!token1OnAvailableList && token2OnAvailableList) + return false; + + *result = false; + return false; +} + +bool CompletionComparer::compareTables(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + if (!helper->parsedQuery || helper->parsedQuery->queryType != SqliteQueryType::Select) + return compareValues(token1, token2); + + if (helper->context == CompletionHelper::Context::SELECT_FROM) + { + // In case the table was already mentioned in any FROM clause, we push it back. + bool token1OnAvailableList = availableTableNames.contains(token1->value); + bool token2OnAvailableList = availableTableNames.contains(token2->value); + if (token1OnAvailableList && !token2OnAvailableList) + return false; + + if (!token1OnAvailableList && token2OnAvailableList) + return true; + } + + bool ok; + bool result = compareByContext(token1->value, token2->value, contextTables, &ok); + if (ok) + return result; + + result = compareByContext(token1->contextInfo, token2->contextInfo, contextDatabases, &ok); + if (ok) + return result; + + result = compareByContext(token1->value, token2->value, parentContextTables, &ok); + if (ok) + return result; + + result = compareByContext(token1->contextInfo, token2->contextInfo, parentContextDatabases, &ok); + if (ok) + return result; + + return compareValues(token1->value, token2->value, true); +} + +bool CompletionComparer::compareIndexes(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + return compareValues(token1, token2, true); +} + +bool CompletionComparer::compareTriggers(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + return compareValues(token1, token2); +} + +bool CompletionComparer::compareViews(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + return compareValues(token1, token2); +} + +bool CompletionComparer::compareDatabases(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + if (!helper->parsedQuery || helper->parsedQuery->queryType != SqliteQueryType::Select) + return compareValues(token1, token2); + + return compareByContext(token1->value, token2->value, {contextDatabases, parentContextDatabases}); +} + +bool CompletionComparer::compareValues(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool handleSystemNames) +{ + return compareValues(token1->value, token2->value, handleSystemNames); +} + +bool CompletionComparer::compareValues(const QString &token1, const QString &token2, bool handleSystemNames) +{ + //qDebug() << "comparing" << token1 << "and" << token2 << "=" << token1.compare(token2, Qt::CaseInsensitive); + if (handleSystemNames) + { + bool firstIsSystem = token1.toLower().startsWith("sqlite_"); + bool secondIsSystem = token2.toLower().startsWith("sqlite_"); + if (firstIsSystem && !secondIsSystem) + return false; + + if (!firstIsSystem && secondIsSystem) + return true; + } + + return token1.compare(token2, Qt::CaseInsensitive) < 0; +} + +bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QStringList &contextValues, bool *ok) +{ + return compareByContext(token1, token2, contextValues, false, ok); +} + +bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QList &contextValues, bool *ok) +{ + return compareByContext(token1, token2, contextValues, false, ok); +} + +bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QStringList &contextValues, bool handleSystemNames, bool* ok) +{ + if (ok) + *ok = true; + + bool localOk = false; + bool result = compareByContextOnly(token1, token2, contextValues, handleSystemNames, &localOk); + + if (localOk) + return result; + + // Otherwise we compare by value. + if (ok) + *ok = false; + + return compareValues(token1, token2, handleSystemNames); +} + +bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QList& contextValues, bool handleSystemNames, bool* ok) +{ + if (ok) + *ok = true; + + bool localOk = false; + bool result; + + for (const QStringList& ctxValues : contextValues) + { + result = compareByContextOnly(token1, token2, ctxValues, handleSystemNames, &localOk); + if (localOk) + return result; + } + + // Otherwise we compare by value. + if (ok) + *ok = false; + + return compareValues(token1, token2, handleSystemNames); +} + +bool CompletionComparer::compareByContextOnly(const QString &token1, const QString &token2, const QStringList &contextValues, bool handleSystemNames, bool *ok) +{ + *ok = true; + + bool token1InContext = contextValues.contains(token1); + bool token2InContext = contextValues.contains(token2); + + // token1 < token2 is true only if token1 is in context and token2 is not. + // This means that token1 will be on the list before token2. + if (token1InContext && !token2InContext) + return true; + + // If token2 is in context, but token1 is not, then it's definite false. + if (!token1InContext && token2InContext) + return false; + + if (handleSystemNames) + { + bool firstIsSystem = token1.toLower().startsWith("sqlite_"); + bool secondIsSystem = token2.toLower().startsWith("sqlite_"); + if (firstIsSystem && !secondIsSystem) + return false; + + if (!firstIsSystem && secondIsSystem) + return true; + } + + *ok = false; + return false; +} + +bool CompletionComparer::isTokenOnAvailableList(const ExpectedTokenPtr &token) +{ + return isTokenOnColumnList(token, helper->selectAvailableColumns); +} + +bool CompletionComparer::isTokenOnParentAvailableList(const ExpectedTokenPtr &token) +{ + return isTokenOnColumnList(token, helper->parentSelectAvailableColumns); +} + +bool CompletionComparer::isTokenOnResultColumns(const ExpectedTokenPtr &token) +{ + return isTokenOnColumnList(token, resultColumns); +} + +bool CompletionComparer::isTokenOnColumnList(const ExpectedTokenPtr &token, const QList &columnList) +{ + foreach (SelectResolver::Column column, columnList) + { + // If column name doesn't match, then it's not this column + if (token->value.compare(column.column, Qt::CaseInsensitive) != 0) + continue; + + // At this point, column name is matched + if (token->prefix.isNull() && token->contextInfo.isNull()) + { + // No prefix, nor context info, just column name. + return true; + } + + // Table alias or just table name? + QString toCompareWithPrefix; + if (!column.tableAlias.isNull()) + toCompareWithPrefix = column.tableAlias; + else + toCompareWithPrefix = column.table; + + // Do we have actual prefix, or just context information and transparent (null) prefix? + QString prefix; +// if (!token->prefix.isNull()) +// prefix = token->prefix; +// else + prefix = token->contextInfo; + + // Does the table/alias match prefix? + if (prefix.compare(column.table, Qt::CaseInsensitive) == 0) + return true; + } + return false; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/completioncomparer.h b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.h new file mode 100644 index 0000000..d482f3a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.h @@ -0,0 +1,74 @@ +#ifndef COMPLETIONCOMPARER_H +#define COMPLETIONCOMPARER_H + +#include "expectedtoken.h" +#include "dialect.h" +#include "selectresolver.h" + +class CompletionHelper; + +class CompletionComparer +{ + public: + explicit CompletionComparer(CompletionHelper* helper); + + bool operator()(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + + private: + CompletionHelper* helper = nullptr; + Dialect dialect; + /** + * @brief contextDatabases + * Context objects are any names mentioned anywhere in the query at the same level as completion takes place. + * The level means the sub-query level, for example in case of sub selects. + */ + QStringList contextDatabases; + QStringList contextTables; + QStringList contextColumns; + + /** + * @brief parentContextDatabases + * Parent context objects are any names mentioned anywhere in the the query at all upper levels. + */ + QStringList parentContextDatabases; + QStringList parentContextTables; + QStringList parentContextColumns; + QList resultColumns; + + /** + * @brief availableTableNames + * Names of all tables mentioned in FROM clause in the current and all parent select cores. + */ + QStringList availableTableNames; + + void init(); + bool initSelect(); + bool compareColumns(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareColumnsForSelectResCol(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result); + bool compareColumnsForUpdateCol(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result); + bool compareColumnsForDeleteCol(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result); + bool compareColumnsForCreateTable(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result); + bool compareTables(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareIndexes(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareTriggers(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareViews(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareDatabases(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareValues(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool handleSystemNames = false); + bool compareValues(const QString& token1, const QString& token2, bool handleSystemNames = false); + bool compareByContext(const QString &token1, const QString &token2, + const QStringList& contextValues, bool* ok = nullptr); + bool compareByContext(const QString &token1, const QString &token2, + const QList& contextValues, bool* ok = nullptr); + bool compareByContext(const QString &token1, const QString &token2, + const QStringList& contextValues, bool handleSystemNames, bool* ok = nullptr); + bool compareByContext(const QString &token1, const QString &token2, + const QList& contextValues, bool handleSystemNames, bool* ok = nullptr); + bool compareByContextOnly(const QString &token1, const QString &token2, + const QStringList& contextValues, bool handleSystemNames, bool* ok); + bool isTokenOnAvailableList(const ExpectedTokenPtr& token); + bool isTokenOnParentAvailableList(const ExpectedTokenPtr& token); + bool isTokenOnResultColumns(const ExpectedTokenPtr& token); + static bool isTokenOnColumnList(const ExpectedTokenPtr& token, const QList& columnList); +}; + +#endif // COMPLETIONCOMPARER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp b/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp new file mode 100644 index 0000000..99eb334 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp @@ -0,0 +1,1425 @@ +#include "completionhelper.h" +#include "completioncomparer.h" +#include "db/db.h" +#include "parser/keywords.h" +#include "parser/parser.h" +#include "parser/lexer.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "dbattacher.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "services/dbmanager.h" +#include +#include + +QStringList sqlite3Pragmas; +QStringList sqlite2Pragmas; +QStringList sqlite3Functions; +QStringList sqlite2Functions; + +bool CompletionHelper::enableLemonDebug = false; + +CompletionHelper::CompletionHelper(const QString &sql, Db* db) + : CompletionHelper(sql, sql.length(), db) +{ +} + +CompletionHelper::CompletionHelper(const QString &sql, quint32 cursorPos, Db* db) + : db(db), cursorPosition(cursorPos), fullSql(sql) +{ + schemaResolver = new SchemaResolver(db); + selectResolver = new SelectResolver(db, fullSql); + selectResolver->ignoreInvalidNames = true; + dbAttacher = SQLITESTUDIO->createDbAttacher(db); +} + +void CompletionHelper::init() +{ + sqlite3Pragmas << "auto_vacuum" << "automatic_index" << "busy_timeout" << "cache_size" + << "case_sensitive_like" << "checkpoint_fullfsync" << "collation_list" + << "compile_options" << "count_changes" << "data_store_directory" + << "database_list" << "default_cache_size" << "empty_result_callbacks" + << "encoding" << "foreign_key_check" << "foreign_key_list" << "foreign_keys" + << "freelist_count" << "full_column_names" << "fullfsync" + << "ignore_check_constraints" << "incremental_vacuum" << "index_info" + << "index_list" << "integrity_check" << "journal_mode" << "journal_size_limit" + << "legacy_file_format" << "locking_mode" << "max_page_count" << "page_count" + << "page_size" << "quick_check" << "read_uncommitted" << "recursive_triggers" + << "reverse_unordered_selects" << "schema_version" << "secure_delete" + << "short_column_names" << "shrink_memory" << "synchronous" << "table_info" + << "temp_store" << "temp_store_directory" << "user_version" + << "wal_autocheckpoint" << "wal_checkpoint" << "writable_schema"; + + sqlite2Pragmas << "cache_size" << "count_changes" << "database_list" << "default_cache_size" + << "default_synchronous" << "default_temp_store" << "empty_result_callbacks" + << "foreign_key_list" << "full_column_names" << "index_info" << "index_list" + << "integrity_check" << "parser_trace" << "show_datatypes" << "synchronous" + << "table_info" << "temp_store"; + + sqlite3Functions << "avg(X)" << "count(X)" << "count(*)" << "group_concat(X)" + << "group_concat(X,Y)" << "max(X)" << "min(X)" << "sum(X)" << "total(X)" + << "abs(X)" << "changes()" << "char(X1,X2,...,XN)" << "coalesce(X,Y,...)" + << "glob(X,Y)" << "ifnull(X,Y)" << "instr(X,Y)" << "hex(X)" + << "last_insert_rowid()" << "length(X)" << "like(X,Y)" << "like(X,Y,Z)" + << "load_extension(X,Y)" << "lower(X)" << "ltrim(X)" << "ltrim(X,Y)" + << "max(X,Y,...)" << "min(X,Y,...)" << "nullif(X,Y)" << "quote(X)" + << "random()" << "randomblob(N)" << "hex(randomblob(16))" + << "lower(hex(randomblob(16)))" << "replace(X,Y,Z)" << "round(X)" + << "round(X,Y)" << "rtrim(X)" << "rtrim(X,Y)" << "soundex(X)" + << "sqlite_compileoption_get(N)" << "sqlite_compileoption_used(X)" + << "sqlite_source_id()" << "sqlite_version()" << "substr(X,Y,Z)" + << "substr(X,Y)" << "total_changes()" << "trim(X)" << "trim(X,Y)" + << "typeof(X)" << "unicode(X)" << "upper(X)" << "zeroblob(N)" + << "date(timestr,mod,mod,...)" << "time(timestr,mod,mod,...)" + << "datetime(timestr,mod,mod,...)" << "julianday(timestr,mod,mod,...)" + << "strftime(format,timestr,mod,mod,...)" << "likelihood(X,Y)" + << "likely(X)" << "unlikely(X)"; + + sqlite2Functions << "abs(X)" << "coalesce(X,Y,...)" << "glob(X,Y)" << "ifnull(X,Y)" + << "last_insert_rowid()" << "length(X)" << "like(X,Y)" << "lower(X)" + << "max(X,Y,...)" << "min(X,Y,...)" << "nullif(X,Y)" << "random(*)" + << "round(X,)" << "round(X,Y)" << "soundex(X)" << "sqlite_version(*)" + << "substr(X,Y,Z)" << "typeof(X)" << "upper(X)" << "avg(X)" << "count(X)" + << "count(*)" << "max(X)" << "min(X)" << "sum(X)"; + + sqlite2Pragmas.sort(); + sqlite3Pragmas.sort(); + sqlite2Functions.sort(); + sqlite3Functions.sort(); +} + +CompletionHelper::~CompletionHelper() +{ + if (schemaResolver) + { + delete schemaResolver; + schemaResolver = nullptr; + } + + if (selectResolver) + { + delete selectResolver; + selectResolver = nullptr; + } + + if (dbAttacher) + { + delete dbAttacher; + dbAttacher = nullptr; + } +} + +CompletionHelper::Results CompletionHelper::getExpectedTokens() +{ + if (!db || !db->isValid()) + return Results(); + + // Get SQL up to the current cursor position. + QString adjustedSql = fullSql.mid(0, cursorPosition); + + // If asked for completion when being in the middle of keyword or ID, + // then remove that unfinished keyword/ID from sql and put it into + // the final filter - to be used at the end of this method. + QString finalFilter = QString::null; + bool wrappedFilter = false; + adjustedSql = removeStartedToken(adjustedSql, finalFilter, wrappedFilter); + + // Parse SQL up to cursor position, get accepted tokens and tokens that were parsed. + Parser parser(db->getDialect()); + TokenList tokens = parser.getNextTokenCandidates(adjustedSql); + TokenList parsedTokens = parser.getParsedTokens(); + + // Parse the full sql in regular mode to extract query statement + // for the results comparer and table-alias mapping. + parseFullSql(); + + // Collect used db names in original query (before using attach names) + collectOtherDatabases(); + + // Handle transparent db attaching + attachDatabases(); + + // Get previous ID tokens (db and table) if any + extractPreviousIdTokens(parsedTokens); + + // Now, that we have parsed query, we can extract some useful information + // depending on the type of query we have. + extractQueryAdditionalInfo(); + + // Convert accepted tokens to expected tokens + QList results; + foreach (TokenPtr token, tokens) + results += getExpectedTokens(token); + + // Filter redundant tokens from results + filterContextKeywords(results, tokens); + filterOtherId(results, tokens); + filterDuplicates(results); + + // ...and sort the output. + sort(results); + + // Detach any databases attached for the completer needs + detachDatabases(); + + Results complexResult; + complexResult.expectedTokens = results; + complexResult.partialToken = finalFilter; + complexResult.wrappedToken = wrappedFilter; + return complexResult; +} + +QList CompletionHelper::getExpectedTokens(TokenPtr token) +{ + QList results; + + // Initial conditions + if (previousId) + { + if (!token->isDbObjectType()) + return results; + + if (twoIdsBack && token->type != Token::CTX_COLUMN) + return results; + } + + // Main routines + switch (token->type) + { + case Token::CTX_ROWID_KW: + results += getExpectedToken(ExpectedToken::KEYWORD, token->value); + break; + case Token::CTX_NEW_KW: + { + if (context == Context::CREATE_TRIGGER) + results += getExpectedToken(ExpectedToken::TABLE, "new", QString::null, tr("New row reference"), 1); + + break; + } + case Token::CTX_OLD_KW: + { + if (context == Context::CREATE_TRIGGER) + results += getExpectedToken(ExpectedToken::TABLE, "old", QString::null, tr("Old row reference"), 1); + + break; + } + case Token::CTX_TABLE_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New table name")); + break; + case Token::CTX_INDEX_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New index name")); + break; + case Token::CTX_VIEW_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New view name")); + break; + case Token::CTX_TRIGGER_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New trigger name")); + break; + case Token::CTX_ALIAS: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Table or column alias")); + break; + case Token::CTX_TRANSACTION: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("transaction name")); + break; + case Token::CTX_COLUMN_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New column name")); + break; + case Token::CTX_COLUMN_TYPE: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Column data type")); + break; + case Token::CTX_CONSTRAINT: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Constraint name")); + break; + case Token::CTX_FK_MATCH: + { + foreach (QString kw, getFkMatchKeywords()) + results += getExpectedToken(ExpectedToken::KEYWORD, kw); + + break; + } + case Token::CTX_PRAGMA: + results += getPragmas(db->getDialect()); + break; + case Token::CTX_ERROR_MESSAGE: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Error message")); + break; + case Token::CTX_COLUMN: + { + results += getColumns(); + break; + } + case Token::CTX_TABLE: + { + results += getTables(); + break; + } + case Token::CTX_INDEX: + { + results += getIndexes(); + break; + } + case Token::CTX_TRIGGER: + { + results += getTriggers(); + break; + } + case Token::CTX_VIEW: + { + results += getViews(); + break; + } + case Token::CTX_DATABASE: + { + results += getDatabases(); + break; + } + case Token::CTX_FUNCTION: + { + results += getFunctions(db); + break; + } + case Token::CTX_COLLATION: + { + if (db->getDialect() == Dialect::Sqlite2) + { + // SQLite 2 doesn't really support collation. It has collations + // in grammar, but doesn't make use of them. There's no list + // of collations to be suggested. + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Collation name")); + } + else + { + results += getCollations(); + } + break; + } + case Token::CTX_JOIN_OPTS: + { + foreach (QString joinKw, getJoinKeywords()) + results += getExpectedToken(ExpectedToken::KEYWORD, joinKw); + break; + } + case Token::OTHER: + results += getExpectedToken(ExpectedToken::OTHER, QString::null, QString::null, tr("Any word")); + break; + case Token::STRING: + results += getExpectedToken(ExpectedToken::STRING); + break; + case Token::FLOAT: + results += getExpectedToken(ExpectedToken::NUMBER); + break; + case Token::INTEGER: + results += getExpectedToken(ExpectedToken::NUMBER); + break; + case Token::OPERATOR: + results += getExpectedToken(ExpectedToken::OPERATOR, token->value); + break; + case Token::PAR_LEFT: + results += getExpectedToken(ExpectedToken::OPERATOR, "("); + break; + case Token::PAR_RIGHT: + results += getExpectedToken(ExpectedToken::OPERATOR, ")"); + break; + case Token::BLOB: + results += getExpectedToken(ExpectedToken::BLOB); + break; + case Token::KEYWORD: + results += getExpectedToken(ExpectedToken::KEYWORD, token->value); + break; + case Token::INVALID: + // No-op + break; + case Token::BIND_PARAM: + // No-op + break; + case Token::SPACE: + // No-op + break; + case Token::COMMENT: + // No-op + break; + } + + return results; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type) +{ + ExpectedToken* token = new ExpectedToken(); + token->type = type; + return ExpectedTokenPtr(token); +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value) +{ + ExpectedTokenPtr token = getExpectedToken(type); + token->value = value; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + int priority) +{ + ExpectedTokenPtr token = getExpectedToken(type, value); + token->priority = priority; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo) +{ + ExpectedTokenPtr token = getExpectedToken(type, value); + token->contextInfo = contextInfo; + token->label = contextInfo; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, int priority) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo); + token->priority = priority; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, + const QString& label) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo); + token->label = label; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, + const QString& label, int priority) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo, label); + token->priority = priority; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, + const QString& label, + const QString& prefix) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo, label); + token->prefix = prefix; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, + const QString& label, + const QString& prefix, + int priority) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo, label, prefix); + token->priority = priority; + return token; +} + +bool CompletionHelper::validatePreviousIdForGetObjects(QString* dbName) +{ + QString localDbName; + if (previousId) + { + localDbName = previousId->value; + QStringList databases = schemaResolver->getDatabases().toList(); + databases += DBLIST->getDbNames(); + if (!databases.contains(localDbName, Qt::CaseInsensitive)) + return false; // if db is not on the set, then getObjects() would return empty list anyway; + + if (dbName) + *dbName = localDbName; + } + return true; +} + +QList CompletionHelper::getTables() +{ + QString dbName; + if (!validatePreviousIdForGetObjects(&dbName)) + return QList(); + + QList tables = getObjects(ExpectedToken::TABLE); + for (const QString& otherDb : otherDatabasesToLookupFor) + tables += getObjects(ExpectedToken::TABLE, otherDb); + + tables += getExpectedToken(ExpectedToken::TABLE, "sqlite_master", dbName); + tables += getExpectedToken(ExpectedToken::TABLE, "sqlite_temp_master", dbName); + return tables; +} + +QList CompletionHelper::getIndexes() +{ + if (!validatePreviousIdForGetObjects()) + return QList(); + + return getObjects(ExpectedToken::INDEX); +} + +QList CompletionHelper::getTriggers() +{ + if (!validatePreviousIdForGetObjects()) + return QList(); + + return getObjects(ExpectedToken::TRIGGER); +} + +QList CompletionHelper::getViews() +{ + if (!validatePreviousIdForGetObjects()) + return QList(); + + return getObjects(ExpectedToken::VIEW); +} + +QList CompletionHelper::getDatabases() +{ + QList results; + + results += getExpectedToken(ExpectedToken::DATABASE, "main", "main", tr("Default database")); + results += getExpectedToken(ExpectedToken::DATABASE, "temp", "temp", tr("Temporary objects database")); + + QSet databases = schemaResolver->getDatabases(); + foreach (QString dbName, databases) + { + if (dbAttacher->getDbNameToAttach().containsRight(dbName, Qt::CaseInsensitive)) + continue; + + results += getExpectedToken(ExpectedToken::DATABASE, dbName); + } + + Dialect dialect = db->getDialect(); + + foreach (Db* otherDb, DBLIST->getValidDbList()) + { + if (otherDb->getDialect() != dialect) + continue; + + results += getExpectedToken(ExpectedToken::DATABASE, otherDb->getName()); + } + + return results; +} + +QList CompletionHelper::getObjects(ExpectedToken::Type type) +{ + if (previousId) + return getObjects(type, previousId->value); + else + return getObjects(type, QString()); +} + +QList CompletionHelper::getObjects(ExpectedToken::Type type, const QString& database) +{ + QString dbName; + QString originalDbName; + if (!database.isNull()) + { + dbName = translateDatabase(database); + originalDbName = database; + } + + QString typeStr; + switch (type) + { + case ExpectedToken::TABLE: + typeStr = "table"; + break; + case ExpectedToken::INDEX: + typeStr = "index"; + break; + case ExpectedToken::TRIGGER: + typeStr = "trigger"; + break; + case ExpectedToken::VIEW: + typeStr = "view"; + break; + default: + qWarning() << "Invalid type passed to CompletionHelper::getObject()."; + return QList(); + } + + QList results; + foreach (QString object, schemaResolver->getObjects(dbName, typeStr)) + results << getExpectedToken(type, object, originalDbName); + + return results; +} + +QList CompletionHelper::getColumns() +{ + QList results; + if (previousId) + { + if (twoIdsBack) + results += getColumns(twoIdsBack->value, previousId->value); + else + results += getColumns(previousId->value); + } + else + { + results += getColumnsNoPrefix(); + } + + if (favoredColumnNames.size() > 0) + results += getFavoredColumns(results); + + return results; +} + +QList CompletionHelper::getColumnsNoPrefix() +{ + QList results; + + // Use available columns for other databases (transparently attached) + QString ctx; + for (SelectResolver::Column& column : selectAvailableColumns) + { + if (column.database.isNull()) + continue; + + if (column.tableAlias.isNull()) + ctx = translateDatabaseBack(column.database)+"."+column.table; + else + ctx = column.tableAlias+" = "+translateDatabaseBack(column.database)+"."+column.table; + + results << getExpectedToken(ExpectedToken::COLUMN, column.column, ctx); + } + + // No db or table provided. For each column its table is remembered, + // so in case some column repeats in more than one table, then we need + // to add prefix for the completion proposal. + QHash columnList; + + // Getting all tables for main db. If any column repeats in many tables, + // then tables are stored as a list for the same column. + foreach (QString table, schemaResolver->getTables(QString::null)) + foreach (QString column, schemaResolver->getTableColumns(table)) + columnList[column] += table; + + // Now, for each column the expected token is created. + // If a column occured in more tables, then multiple expected tokens + // are created to reflect all possible tables. + QHashIterator it(columnList); + while (it.hasNext()) + { + it.next(); + results << getColumnsNoPrefix(it.key(), it.value()); + } + + return results; +} + +QList CompletionHelper::getColumnsNoPrefix(const QString& column, const QStringList& tables) +{ + QList results; + + QStringList availableTableNames; + foreach (SelectResolver::Table resolverTable, selectAvailableTables + parentSelectAvailableTables) + { + // This method is called only when collecting columns of tables in "main" database. + // If here we have resolved table from other database, we don't compare it. + if (!resolverTable.database.isNull() && resolverTable.database.toLower() != "main") + continue; + + availableTableNames += resolverTable.table; + } + + int availableTableCount = 0; + foreach (QString availTable, availableTableNames) + if (tables.contains(availTable)) + availableTableCount++; + + foreach (QString table, tables) + { + // Table prefix is used only if there is more than one table in FROM clause + // that has this column, or table alias was used. + if (!currentSelectCore || (availableTableCount <= 1 && !tableToAlias.contains(table))) + results << getExpectedToken(ExpectedToken::COLUMN, column, table); + else + { + // The prefix table might need translation to an alias. + QString prefix = table; + QString label = table; + if (tableToAlias.contains(prefix)) + { + foreach (prefix, tableToAlias[prefix]) + { + label = prefix+" = "+table; + results << getExpectedToken(ExpectedToken::COLUMN, column, table, label, prefix); + } + } + else + results << getExpectedToken(ExpectedToken::COLUMN, column, table, label, prefix); + } + } + + return results; +} + +QList CompletionHelper::getColumns(const QString &prefixTable) +{ + QList results; + + QString label = prefixTable; + QString table = prefixTable; + QString dbName; + if (aliasToTable.contains(prefixTable)) + { + Table tableAndDb = aliasToTable.value(prefixTable); + table = tableAndDb.getTable(); + dbName = tableAndDb.getDatabase(); + label = prefixTable+" = "+table; + } + + // CREATE TRIGGER has a special "old" and "new" keywords as aliases for deleted/inserted/updated rows. + // They should refer to a table that the trigger is created for. + QString tableLower = table; + if (context == Context::CREATE_TRIGGER && (tableLower == "old" || tableLower == "new")) + { + if (!createTriggerTable.isNull()) + { + table = createTriggerTable; + label = createTriggerTable; + } + else + { + SqliteCreateTriggerPtr createTrigger = parsedQuery.dynamicCast(); + if (createTrigger && !createTrigger->table.isNull()) + { + table = createTrigger->table; + label = table; + } + } + } + + // Get columns for given table in main db. + foreach (const QString& column, schemaResolver->getTableColumns(dbName, table)) + results << getExpectedToken(ExpectedToken::COLUMN, column, table, label); + + return results; +} + +QList CompletionHelper::getColumns(const QString &prefixDb, const QString &prefixTable) +{ + QList results; + + // Get columns for given table in given db. + QString context = prefixDb+"."+prefixTable; + foreach (const QString& column, schemaResolver->getTableColumns(translateDatabase(prefixDb), prefixTable)) + results << getExpectedToken(ExpectedToken::COLUMN, column, context); + + return results; +} + +QList CompletionHelper::getFavoredColumns(const QList& resultsSoFar) +{ + // Prepare list that doesn't create duplicates with the results we already have. + // Since results so far have more chance to provide context into, we will keep the original ones + // from results so far and avoid adding then from favored list. + QStringList columnsToAdd = favoredColumnNames; + foreach (const ExpectedTokenPtr& token, resultsSoFar) + { + if (token->prefix.isNull() && columnsToAdd.contains(token->value)) + columnsToAdd.removeOne(token->value); + } + + QString ctxInfo; + if (context == Context::CREATE_TABLE && parsedQuery) + ctxInfo = parsedQuery.dynamicCast()->table; + + QList results; + foreach (const QString& column, columnsToAdd) + results << getExpectedToken(ExpectedToken::COLUMN, column, ctxInfo); + + return results; +} + +QList CompletionHelper::getFunctions(Db* db) +{ + // TODO to do later - make function completion more verbose, + // like what are arguments of the function, etc. + Dialect dialect = db->getDialect(); + + QStringList functions; + if (dialect == Dialect::Sqlite2) + functions = sqlite2Functions; + else + functions = sqlite3Functions; + + for (FunctionManager::ScriptFunction* fn : FUNCTIONS->getScriptFunctionsForDatabase(db->getName())) + functions << fn->toString(); + + for (FunctionManager::NativeFunction* fn : FUNCTIONS->getAllNativeFunctions()) + functions << fn->toString(); + + QList expectedTokens; + foreach (QString function, functions) + expectedTokens += getExpectedToken(ExpectedToken::FUNCTION, function); + + return expectedTokens; +} + +QList CompletionHelper::getPragmas(Dialect dialect) +{ + QStringList pragmas; + if (dialect == Dialect::Sqlite2) + pragmas = sqlite2Pragmas; + else + pragmas = sqlite3Pragmas; + + QList expectedTokens; + foreach (QString pragma, pragmas) + expectedTokens += getExpectedToken(ExpectedToken::PRAGMA, pragma); + + return expectedTokens; +} + +QList CompletionHelper::getCollations() +{ + SqlQueryPtr results = db->exec("PRAGMA collation_list;"); + if (results->isError()) + { + qWarning() << "Got error when trying to get collation_list: " + << results->getErrorText(); + } + QList expectedTokens; + foreach (SqlResultsRowPtr row, results->getAll()) + expectedTokens += getExpectedToken(ExpectedToken::COLLATION, row->value("name").toString()); + + return expectedTokens; +} + +TokenPtr CompletionHelper::getPreviousDbOrTable(const TokenList &parsedTokens) +{ + // First check if we even get to deal with db.table or table.column. + // In order to do that we iterate backwards starting from the end. + TokenPtr token; + QListIterator it(parsedTokens); + it.toBack(); + + if (!it.hasPrevious()) + return TokenPtr(); // No tokens at all. Shouldn't really happen. + + token = it.previous(); + + // Skip spaces and comments + while ((token->type == Token::SPACE || token->type == Token::COMMENT) && it.hasPrevious()) + token = it.previous(); + + // Check if first non-space and non-comment token is our dot. + if (token->type != Token::OPERATOR || token->value != ".") + return TokenPtr(); // Previous token is not a dot. + + // We have a dot, now let's look for another token before. + if (!it.hasPrevious()) + return TokenPtr(); // No more tokens left in front. + + token = it.previous(); + + // Skip spaces and comments + while ((token->type == Token::SPACE || token->type == Token::COMMENT) && it.hasPrevious()) + token = it.previous(); + + // Check if this token is an ID. + if (token->type != Token::OTHER) + return TokenPtr(); // One more token before is not an ID. + + // Okay, so now we now we have "some_id." + // Since this method is called in known context (looking for either db or table), + // we don't need to find out what the "some_id" is. We simple return its value. + return token; +} + +void CompletionHelper::attachDatabases() +{ + if (!parsedQuery) + return; + + if (!dbAttacher->attachDatabases(parsedQuery)) + return; + + QString query = parsedQuery->detokenize(); + + Parser parser(db->getDialect()); + if (parser.parse(query, true) && !parser.getQueries().isEmpty()) + parsedQuery = parser.getQueries().first(); +} + +void CompletionHelper::detachDatabases() +{ + dbAttacher->detachDatabases(); +} + +QString CompletionHelper::translateDatabase(const QString& dbName) +{ + if (!dbAttacher->getDbNameToAttach().containsLeft(dbName, Qt::CaseInsensitive)) + return dbName; + + return dbAttacher->getDbNameToAttach().valueByLeft(dbName, Qt::CaseInsensitive); +} + +QString CompletionHelper::translateDatabaseBack(const QString& dbName) +{ + if (!dbAttacher->getDbNameToAttach().containsRight(dbName, Qt::CaseInsensitive)) + return dbName; + + return dbAttacher->getDbNameToAttach().valueByRight(dbName, Qt::CaseInsensitive); +} + +void CompletionHelper::collectOtherDatabases() +{ + otherDatabasesToLookupFor.clear(); + if (!parsedQuery) + return; + + otherDatabasesToLookupFor = parsedQuery->getContextDatabases(); +} + +QString CompletionHelper::removeStartedToken(const QString& adjustedSql, QString& finalFilter, bool& wrappedFilter) +{ + QString result = adjustedSql; + + Lexer lexer(db->getDialect()); + TokenList tokens = lexer.tokenize(adjustedSql); + if (tokens.size() == 0) + return result; + + TokenPtr lastToken = tokens.last(); + + if (isFilterType(lastToken->type)) + { + result = Lexer::detokenize(tokens.mid(0, tokens.size()-1)); + finalFilter = lastToken->value; + + if (finalFilter.length() > 0 && isWrapperChar(finalFilter[0], db->getDialect())) + { + finalFilter = finalFilter.mid(1); + wrappedFilter = true; + } + } + return result; +} + +void CompletionHelper::filterContextKeywords(QList &resultsSoFar, const TokenList &tokens) +{ + bool wasJoinKw = false; + bool wasFkMatchKw = false; + foreach (TokenPtr token, tokens) + { + if (token->type == Token::CTX_JOIN_OPTS) + wasJoinKw = true; + + if (token->type == Token::CTX_FK_MATCH) + wasFkMatchKw = true; + } + + if (wasJoinKw && wasFkMatchKw) + return; + + QMutableListIterator it(resultsSoFar); + while (it.hasNext()) + { + ExpectedTokenPtr token = it.next(); + if (token->type != ExpectedToken::KEYWORD) + continue; + + if ( + (!wasJoinKw && isJoinKeyword(token->value)) || + (!wasFkMatchKw && isFkMatchKeyword(token->value)) + ) + it.remove(); + } +} + +void CompletionHelper::filterOtherId(QList &resultsSoFar, const TokenList &tokens) +{ + bool wasCtx = false; + foreach (TokenPtr token, tokens) + { + switch (token->type) + { + case Token::CTX_COLUMN: + case Token::CTX_TABLE: + case Token::CTX_DATABASE: + case Token::CTX_FUNCTION: + case Token::CTX_COLLATION: + case Token::CTX_INDEX: + case Token::CTX_TRIGGER: + case Token::CTX_VIEW: + case Token::CTX_JOIN_OPTS: + case Token::CTX_TABLE_NEW: + case Token::CTX_INDEX_NEW: + case Token::CTX_VIEW_NEW: + case Token::CTX_TRIGGER_NEW: + case Token::CTX_ALIAS: + case Token::CTX_TRANSACTION: + case Token::CTX_COLUMN_NEW: + case Token::CTX_COLUMN_TYPE: + case Token::CTX_CONSTRAINT: + case Token::CTX_FK_MATCH: + case Token::CTX_PRAGMA: + case Token::CTX_ROWID_KW: + case Token::CTX_NEW_KW: + case Token::CTX_OLD_KW: + case Token::CTX_ERROR_MESSAGE: + wasCtx = true; + break; + default: + break; + } + if (wasCtx) + break; + } + + if (!wasCtx) + return; + + QMutableListIterator it(resultsSoFar); + while (it.hasNext()) + { + ExpectedTokenPtr token = it.next(); + if (token->type == ExpectedToken::OTHER) + it.remove(); + } +} + +void CompletionHelper::filterDuplicates(QList& resultsSoFar) +{ + QSet set = resultsSoFar.toSet(); + resultsSoFar = set.toList(); +} + +void CompletionHelper::applyFilter(QList& resultsSoFar, const QString& filter) +{ + if (filter.isEmpty()) + return; + + QMutableListIterator it(resultsSoFar); + while (it.hasNext()) + { + ExpectedTokenPtr token = it.next(); + if (!token->value.startsWith(filter, Qt::CaseInsensitive)) + it.remove(); + } +} + +bool CompletionHelper::isFilterType(Token::Type type) +{ + switch (type) + { + case Token::COMMENT: + case Token::SPACE: + case Token::PAR_LEFT: + case Token::PAR_RIGHT: + case Token::OPERATOR: + return false; + default: + return true; + } +} + +void CompletionHelper::parseFullSql() +{ + Dialect dialect = db->getDialect(); + + Parser parser(dialect); + parser.setLemonDebug(enableLemonDebug); + + QString sql = fullSql; + + // Selecting query at cursor position + QString query = getQueryWithPosition(sql, cursorPosition, dialect); + + // Token list of the query. Also useful, not only parsed query. + queryTokens = Lexer::tokenize(query, dialect); + queryTokens.trim(); + + // Completing query + if (!query.trimmed().endsWith(";")) + query += ";"; + + // Parsing query + if (parser.parse(query, true) && !parser.getQueries().isEmpty()) + { + parsedQuery = parser.getQueries().first(); + originalParsedQuery = SqliteQueryPtr(dynamic_cast(parsedQuery->clone())); + } +} + +void CompletionHelper::sort(QList &resultsSoFar) +{ + CompletionComparer comparer(this); + qSort(resultsSoFar.begin(), resultsSoFar.end(), comparer); +} + +void CompletionHelper::extractPreviousIdTokens(const TokenList &parsedTokens) +{ + Dialect dialect = db->getDialect(); + + // The previous ID token (if any) is being used in + // getExpectedToken() and it's always the same token, + // so here we find it just once and reuse it. + previousId = stripObjName(getPreviousDbOrTable(parsedTokens), dialect); + + // In case of column context we need to know if there was + // up to two ID tokens before. If we had one above, + // then we check for one more here. + twoIdsBack.clear(); + if (previousId) + { + int idx = parsedTokens.indexOf(previousId); + TokenList parsedTokensSubSet = parsedTokens.mid(0, idx); + twoIdsBack = stripObjName(getPreviousDbOrTable(parsedTokensSubSet), dialect); + } +} + +void CompletionHelper::extractQueryAdditionalInfo() +{ + if (extractSelectCore()) + { + extractSelectAvailableColumnsAndTables(); + extractTableAliasMap(); + removeDuplicates(parentSelectAvailableColumns); + detectSelectContext(); + } + else if (isInUpdateColumn()) + { + context = Context::UPDATE_COLUMN; + } + else if (isInUpdateWhere()) + { + context = Context::UPDATE_WHERE; + } + else if (isInDeleteWhere()) + { + context = Context::DELETE_WHERE; + } + else if (isInCreateTable()) + { + context = Context::CREATE_TABLE; + extractCreateTableColumns(); + } + else if (isInCreateTrigger()) + { + context = Context::CREATE_TRIGGER; + } + else if (isInExpr()) + { + context = Context::EXPR; + } +} + +void CompletionHelper::detectSelectContext() +{ + QStringList mapNames = {"SELECT", "distinct", "selcollist", "from", "where_opt", "groupby_opt", "having_opt", "orderby_opt", "limit_opt"}; + QList contexts = {Context::SELECT_RESULT_COLUMN, Context::SELECT_FROM, Context::SELECT_WHERE, Context::SELECT_GROUP_BY, + Context::SELECT_HAVING, Context::SELECT_ORDER_BY, Context::SELECT_LIMIT}; + + // Assert that we have exactly 2 more map names, than defined contexts, cause we will start with 3rd map name and 1st context. + Q_ASSERT((mapNames.size() - 2) == contexts.size()); + + for (int i = 2; i < mapNames.size(); i++) + { + if (cursorAfterTokenMaps(currentSelectCore, mapNames.mid(0, i)) && cursorBeforeTokenMaps(currentSelectCore, mapNames.mid(i+1))) + { + context = contexts[i-2]; + break; + } + } +} + +bool CompletionHelper::isInUpdateColumn() +{ + // We will never get here if the subquery SELECT was used anywhere in the query, + // (and the cursor position is in that subselect), because in that case the extractSelectCore() + // will take over the flow before it reaches here. + return isIn(SqliteQueryType::Update, "setlist", "SET"); +} + +bool CompletionHelper::isInUpdateWhere() +{ + return isIn(SqliteQueryType::Update, "where_opt", "WHERE"); +} + +bool CompletionHelper::isInDeleteWhere() +{ + return isIn(SqliteQueryType::Delete, "where_opt", "WHERE"); +} + +bool CompletionHelper::isInCreateTable() +{ + if (!parsedQuery) + { + if (testQueryToken(0, Token::KEYWORD, "CREATE") && + (testQueryToken(1, Token::KEYWORD, "TABLE") || + testQueryToken(2, Token::KEYWORD, "TABLE"))) + { + return true; + } + + return false; + } + + if (parsedQuery->queryType != SqliteQueryType::CreateTable) + return false; + + return true; +} + +bool CompletionHelper::isInCreateTrigger() +{ + if (!parsedQuery) + { + if (testQueryToken(0, Token::KEYWORD, "CREATE") && + (testQueryToken(1, Token::KEYWORD, "TRIGGER") || + testQueryToken(2, Token::KEYWORD, "TRIGGER"))) + { + return true; + } + + return false; + } + + if (parsedQuery->queryType != SqliteQueryType::CreateTrigger) + return false; + + return true; +} + +bool CompletionHelper::isIn(SqliteQueryType queryType, const QString &tokenMapKey, const QString &prefixKeyword) +{ + if (!parsedQuery) + return false; + + if (parsedQuery->queryType != queryType) + return false; + + // We're looking for curPos - 1, because tokens indexing ends in the same, with -1 index. + TokenPtr token = parsedQuery->tokens.atCursorPosition(cursorPosition - 1); + if (!token) + return false; + + if (parsedQuery->tokensMap[tokenMapKey].contains(token)) + return true; + + // In case cursor is just before the requested token map entry, but it is after a whitespace, then we can + // assume, that what's coming next is our token map entry. + if (token->isWhitespace()) + { + int idx = parsedQuery->tokens.indexOf(token); + if (idx < 0) + return false; + + TokenList tokens = parsedQuery->tokens.mid(0, idx + 1); + tokens.trim(); + if (tokens.size() > 0 && tokens.last()->type == Token::KEYWORD && tokens.last()->value.compare(prefixKeyword, Qt::CaseInsensitive) == 0) + return true; + } + + return false; +} + +bool CompletionHelper::isInExpr() +{ + if (!parsedQuery) + return false; + + // Finding in which statement the cursor is positioned + // We're looking for curPos - 1, because tokens indexing ends in the same, with -1 index. + SqliteStatement* stmt = parsedQuery->findStatementWithPosition(cursorPosition - 1); + + // Now going up in statements tree in order to find first expr + while (stmt && !dynamic_cast(stmt)) + stmt = stmt->parentStatement(); + + return (stmt && dynamic_cast(stmt)); +} + +bool CompletionHelper::testQueryToken(int tokenPosition, Token::Type type, const QString& value, Qt::CaseSensitivity cs) +{ + if (tokenPosition >= queryTokens.size()) + return false; + + if (tokenPosition < 0) + return 0; + + TokenPtr token = queryTokens[tokenPosition]; + return (token->type == type && token->value.compare(value, cs) == 0); +} + +bool CompletionHelper::cursorAfterTokenMaps(SqliteStatement* stmt, const QStringList& mapNames) +{ + TokenList tokens; + foreach (const QString& name, mapNames) + { + if (!stmt->tokensMap.contains(name) || stmt->tokensMap[name].size() == 0) + continue; + + tokens = stmt->tokensMap[name]; + tokens.trimRight(); + if (tokens.size() == 0) + continue; + + if (tokens.last()->end >= cursorPosition) + return false; + } + return true; +} + +bool CompletionHelper::cursorBeforeTokenMaps(SqliteStatement* stmt, const QStringList& mapNames) +{ + TokenList tokens; + foreach (const QString& name, mapNames) + { + if (!stmt->tokensMap.contains(name) || stmt->tokensMap[name].size() == 0) + continue; + + tokens = stmt->tokensMap[name]; + tokens.trimLeft(); + if (tokens.size() == 0) + continue; + + if (tokens.first()->start < cursorPosition) + return false; + } + return true; +} +QString CompletionHelper::getCreateTriggerTable() const +{ + return createTriggerTable; +} + +void CompletionHelper::setCreateTriggerTable(const QString& value) +{ + createTriggerTable = value; +} + +DbAttacher* CompletionHelper::getDbAttacher() const +{ + return dbAttacher; +} + +void CompletionHelper::setDbAttacher(DbAttacher* value) +{ + if (dbAttacher) + delete dbAttacher; + + dbAttacher = value; +} + + +bool CompletionHelper::extractSelectCore() +{ + currentSelectCore = extractSelectCore(parsedQuery); + originalCurrentSelectCore = extractSelectCore(originalParsedQuery); + return (currentSelectCore != nullptr); +} + +SqliteSelect::Core* CompletionHelper::extractSelectCore(SqliteQueryPtr query) +{ + if (!query) + return nullptr; + + // Finding in which statement the cursor is positioned + // We're looking for curPos - 1, because tokens indexing ends in the same, with -1 index. + SqliteStatement* stmt = query->findStatementWithPosition(cursorPosition - 1); + + // Now going up in statements tree in order to find first select core + while (stmt && !dynamic_cast(stmt)) + stmt = stmt->parentStatement(); + + if (stmt && dynamic_cast(stmt)) + { + // We found our select core + return dynamic_cast(stmt); + } + + return nullptr; +} + +void CompletionHelper::extractSelectAvailableColumnsAndTables() +{ + selectAvailableColumns = selectResolver->resolveAvailableColumns(currentSelectCore); + selectAvailableTables = selectResolver->resolveTables(currentSelectCore); + + // Now checking for any parent select cores. + SqliteStatement* stmt = currentSelectCore->parentStatement(); + SqliteSelect::Core* parentCore = nullptr; + while (stmt) + { + while (stmt && !dynamic_cast(stmt)) + stmt = stmt->parentStatement(); + + if (!stmt || !dynamic_cast(stmt)) + return; + + // We got another select core at higher level + parentCore = dynamic_cast(stmt); + parentSelectCores += parentCore; + + // Collecting columns and tables + parentSelectAvailableColumns += selectResolver->resolveAvailableColumns(parentCore); + parentSelectAvailableTables += selectResolver->resolveTables(parentCore); + + // Moving on, until we're on top of the syntax tree. + stmt = stmt->parentStatement(); + } +} + +void CompletionHelper::extractTableAliasMap() +{ + foreach (SelectResolver::Column column, selectAvailableColumns) + { + if (column.type != SelectResolver::Column::COLUMN) + continue; + + if (!column.tableAlias.isNull() && !tableToAlias[column.table].contains(column.tableAlias)) + { + tableToAlias[column.table] += column.tableAlias; + aliasToTable[column.tableAlias] = Table(column.database, column.table); + } + } + + // We have sorted list of available columns from parent selects. + // Given the above, we can extract table aliases in an order from deepest + // to shallowest, skipping any duplicates, becase the deeper alias is mentioned, + // the higher is its priority. + foreach (SelectResolver::Column column, parentSelectAvailableColumns) + { + if (column.type != SelectResolver::Column::COLUMN) + continue; + + if (tableToAlias.contains(column.table)) + continue; + + if (!column.tableAlias.isNull() && !tableToAlias[column.table].contains(column.tableAlias)) + { + tableToAlias[column.table] += column.tableAlias; + aliasToTable[column.tableAlias] = Table(column.database, column.table); + } + } +} + +void CompletionHelper::extractCreateTableColumns() +{ + if (!parsedQuery) + return; + + SqliteCreateTablePtr createTable = parsedQuery.dynamicCast(); + foreach (SqliteCreateTable::Column* col, createTable->columns) + favoredColumnNames << col->name; +} + +QList CompletionHelper::Results::filtered() +{ + QList tokens = expectedTokens; + applyFilter(tokens, partialToken); + return tokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/completionhelper.h b/SQLiteStudio3/coreSQLiteStudio/completionhelper.h new file mode 100644 index 0000000..63b7225 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/completionhelper.h @@ -0,0 +1,264 @@ +#ifndef COMPLETIONHELPER_H +#define COMPLETIONHELPER_H + +#include "expectedtoken.h" +#include "schemaresolver.h" +#include "selectresolver.h" +#include "dialect.h" +#include "completioncomparer.h" +#include "parser/ast/sqliteselect.h" +#include "parser/token.h" +#include "db/db.h" +#include "common/strhash.h" +#include "common/table.h" +#include +#include + +class DbAttacher; + +class API_EXPORT CompletionHelper : public QObject +{ + friend class CompletionComparer; + + public: + struct API_EXPORT Results + { + QList filtered(); + + QList expectedTokens; + QString partialToken; + bool wrappedToken = false; + }; + + CompletionHelper(const QString& sql, Db* db); + CompletionHelper(const QString& sql, quint32 cursorPos, Db* db); + ~CompletionHelper(); + + static void applyFilter(QList &results, const QString &filter); + static void init(); + + Results getExpectedTokens(); + + DbAttacher* getDbAttacher() const; + void setDbAttacher(DbAttacher* value); + + static bool enableLemonDebug; + + QString getCreateTriggerTable() const; + void setCreateTriggerTable(const QString& value); + + private: + enum class Context + { + NONE, + SELECT_RESULT_COLUMN, + SELECT_FROM, + SELECT_WHERE, + SELECT_GROUP_BY, + SELECT_HAVING, + SELECT_ORDER_BY, + SELECT_LIMIT, + UPDATE_COLUMN, + UPDATE_WHERE, + DELETE_WHERE, + CREATE_TABLE, + CREATE_TRIGGER, + EXPR + }; + + QList getExpectedTokens(TokenPtr token); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString& value); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString& value, + int priority); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString& value, + const QString& contextInfo); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString& value, + const QString& contextInfo, int priority); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString &contextInfo, const QString &label); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString &contextInfo, const QString &label, + int priority); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString &contextInfo, const QString &label, + const QString &prefix); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString &contextInfo, const QString &label, + const QString &prefix, int priority); + bool validatePreviousIdForGetObjects(QString* dbName = nullptr); + QList getTables(); + QList getIndexes(); + QList getTriggers(); + QList getViews(); + QList getDatabases(); + QList getObjects(ExpectedToken::Type type); + QList getObjects(ExpectedToken::Type type, const QString& database); + QList getColumns(); + QList getColumnsNoPrefix(); + QList getColumnsNoPrefix(const QString &column, const QStringList &tables); + QList getColumns(const QString& prefixTable); + QList getColumns(const QString& prefixDb, const QString& prefixTable); + QList getFavoredColumns(const QList& resultsSoFar); + + QList getPragmas(Dialect dialect); + QList getFunctions(Db* db); + QList getCollations(); + TokenPtr getPreviousDbOrTable(const TokenList& parsedTokens); + void attachDatabases(); + void detachDatabases(); + QString translateDatabase(const QString& dbName); + QString translateDatabaseBack(const QString& dbName); + void collectOtherDatabases(); + QString removeStartedToken(const QString& adjustedSql, QString &finalFilter, bool& wrappedFilter); + void filterContextKeywords(QList &results, const TokenList& tokens); + void filterOtherId(QList &results, const TokenList& tokens); + void filterDuplicates(QList &results); + bool isFilterType(Token::Type type); + void parseFullSql(); + void sort(QList &results); + void extractPreviousIdTokens(const TokenList& parsedTokens); + void extractQueryAdditionalInfo(); + void extractSelectAvailableColumnsAndTables(); + bool extractSelectCore(); + SqliteSelect::Core* extractSelectCore(SqliteQueryPtr query); + void extractTableAliasMap(); + void extractCreateTableColumns(); + void detectSelectContext(); + bool isInUpdateColumn(); + bool isInUpdateWhere(); + bool isInDeleteWhere(); + bool isInCreateTable(); + bool isInCreateTrigger(); + bool isIn(SqliteQueryType queryType, const QString& tokenMapKey, const QString &prefixKeyword); + bool isInExpr(); + bool testQueryToken(int tokenPosition, Token::Type type, const QString& value, Qt::CaseSensitivity cs = Qt::CaseInsensitive); + bool cursorAfterTokenMaps(SqliteStatement* stmt, const QStringList& mapNames); + bool cursorBeforeTokenMaps(SqliteStatement* stmt, const QStringList& mapNames); + + template + bool cursorFitsInCollection(const QList& collection) + { + if (collection.size() == 0) + return false; + + T* firstStmt = collection.first(); + T* lastStmt = collection.last(); + + int startIdx = -1; + int endIdx = -1; + if (firstStmt && firstStmt->tokens.size() > 0) + startIdx = firstStmt->tokens.first()->start; + + if (lastStmt && lastStmt->tokens.size() > 0) + endIdx = lastStmt->tokens.last()->end; + + if (startIdx < 0 || endIdx < 0) + return false; + + return (cursorPosition >= startIdx && cursorPosition <= endIdx); + } + + template + bool cursorFitsInStatement(T* stmt) + { + if (!stmt || stmt->tokens.size() == 0) + return false; + + int startIdx = stmt->tokens.first()->start; + int endIdx = stmt->tokens.last()->end; + + return (cursorPosition >= startIdx && cursorPosition <= endIdx); + } + + + Context context = Context::NONE; + Db* db = nullptr; + qint64 cursorPosition; + QString fullSql; + TokenPtr previousId; + TokenPtr twoIdsBack; + TokenList queryTokens; + SqliteQueryPtr parsedQuery; + SqliteQueryPtr originalParsedQuery; + SchemaResolver* schemaResolver = nullptr; + SelectResolver* selectResolver = nullptr; + DbAttacher* dbAttacher = nullptr; + QString createTriggerTable; + + /** + * @brief tableToAlias + * This map maps real table name to its alias. Every table can be typed multiple times + * with different aliases, therefore this map has a list on the right side. + */ + QHash tableToAlias; + + /** + * @brief aliasToTable + * Maps table alias to table's real name. + */ + //QHash aliasToTable; + StrHash

::type p; + tuple(typename no_cref::type a = typename no_cref::type(), typename no_cref::type b = typename no_cref::type(), + typename no_cref::type c = typename no_cref::type(), typename no_cref::type d = typename no_cref::type(), + typename no_cref::type e = typename no_cref::type(), typename no_cref::type f = typename no_cref::type(), + typename no_cref::type g = typename no_cref::type(), typename no_cref::type h = typename no_cref::type(), + typename no_cref::type i = typename no_cref::type(), typename no_cref::type j = typename no_cref::type(), + typename no_cref::type k = typename no_cref::type(), typename no_cref::type l = typename no_cref::type(), + typename no_cref::type m = typename no_cref::type(), typename no_cref::type n = typename no_cref::type(), + typename no_cref::type o = typename no_cref::type(), typename no_cref

aliasToTable; + + /** + * @brief currentSelectCore + * The SqliteSelect::Core object that contains current cursor position. + */ + SqliteSelect::Core* currentSelectCore = nullptr; + + /** + * @brief originalCurrentSelectCore + * + * The same as currentSelectCore, but relates to originalParsedQuery, not just parsedQuery. + */ + SqliteSelect::Core* originalCurrentSelectCore = nullptr; + + /** + * @brief selectAvailableColumns + * Available columns are columns that can be selected basing on what tables are mentioned in FROM clause. + */ + QList selectAvailableColumns; + + /** + * @brief selectAvailableTables + * Availble tables are tables mentioned in FROM clause. + */ + QSet selectAvailableTables; + + /** + * @brief parentSelectCores + * List of all parent select core objects in order: from direct parent, to the oldest parent. + */ + QList parentSelectCores; + + /** + * @brief parentSelectAvailableColumns + * List of all columns available in all tables mentioned in all parent select cores. + */ + QList parentSelectAvailableColumns; + + /** + * @brief parentSelectAvailableTables + * Same as parentSelectAvailableColumns, but for tables in FROM clauses. + */ + QSet parentSelectAvailableTables; + + /** + * @brief favoredColumnNames + * For some contexts there are favored column names, like for example CREATE TABLE + * will favour column names specified so far in its definition. + * Such columns will be added to completion results even they weren't result + * of regular computation. + */ + QStringList favoredColumnNames; + + /** + * @brief Context databases to look for objects in. + * + * If the query uses some other databases (not the currentone), then user might be interested + * also in objects from those databases. This list contains those databases. + */ + QStringList otherDatabasesToLookupFor; +}; + +#endif // COMPLETIONHELPER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder.h b/SQLiteStudio3/coreSQLiteStudio/config_builder.h new file mode 100644 index 0000000..f4e030e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder.h @@ -0,0 +1,76 @@ +#ifndef CFGINTERNALS_H +#define CFGINTERNALS_H + +#include "config_builder/cfgmain.h" +#include "config_builder/cfgcategory.h" +#include "config_builder/cfgentry.h" +#include "config_builder/cfglazyinitializer.h" + +#define CFG_CATEGORIES(Type,Body) _CFG_CATEGORIES_WITH_METANAME_AND_TITLE(Type,Body,"",QString()) + +#define CFG_CATEGORY(Name,Body) \ + _CFG_CATEGORY_WITH_TITLE(Name,Body,QString()) + +#define CFG_ENTRY(Type, Name, ...) CfgTypedEntry Name = CfgTypedEntry(#Name, ##__VA_ARGS__); + +#define CFG_DEFINE(Type) _CFG_DEFINE(Type, true) +#define CFG_DEFINE_RUNTIME(Type) _CFG_DEFINE(Type, false) +#define CFG_LOCAL(Type, Name) Cfg::Type Name = Cfg::Type(false); +#define CFG_LOCAL_PERSISTABLE(Type, Name) Cfg::Type Name = Cfg::Type(true); +#define CFG_DEFINE_LAZY(Type) \ + namespace Cfg\ + {\ + Type* cfgMainInstance##Type = nullptr;\ + void init##Type##Instance()\ + {\ + cfgMainInstance##Type = new Type(true);\ + }\ + Type* get##Type##Instance()\ + {\ + return cfgMainInstance##Type;\ + }\ + CfgLazyInitializer* cfgMainInstance##Type##LazyInit = new CfgLazyInitializer(init##Type##Instance, #Type);\ + } + +#define CFG_INSTANCE(Type) (*Cfg::get##Type##Instance()) + +// Macros below are kind of private. You should not need to use them explicitly. +// They are called from macros above. + +#define _CFG_CATEGORIES_WITH_METANAME(Type,Body,MetaName) \ + _CFG_CATEGORIES_WITH_METANAME_AND_TITLE(Type,Body,MetaName,QString()) + +#define _CFG_CATEGORIES_WITH_TITLE(Type,Body,Title) \ + _CFG_CATEGORIES_WITH_METANAME_AND_TITLE(Type,Body,"",Title) + +#define _CFG_CATEGORIES_WITH_METANAME_AND_TITLE(Type,Body,MetaName,Title) \ + namespace Cfg\ + {\ + struct API_EXPORT Type : public CfgMain\ + {\ + Type(bool persistable) : CfgMain(#Type, persistable, MetaName, Title) {}\ + Body\ + };\ + API_EXPORT Type* get##Type##Instance();\ + } + +#define _CFG_DEFINE(Type, Persistant) \ + namespace Cfg\ + {\ + Type* cfgMainInstance##Type = new Type(Persistant);\ + Type* get##Type##Instance()\ + {\ + return cfgMainInstance##Type;\ + }\ + } + +#define _CFG_CATEGORY_WITH_TITLE(Name,Body,Title) \ + struct API_EXPORT _##Name##Type : public CfgCategory\ + {\ + _##Name##Type() : CfgCategory(#Name, Title) {}\ + Body\ + };\ + _##Name##Type Name; + + +#endif // CFGINTERNALS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.cpp b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.cpp new file mode 100644 index 0000000..a79e08a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.cpp @@ -0,0 +1,99 @@ +#include "cfgcategory.h" +#include "config_builder/cfgmain.h" +#include "config_builder/cfgentry.h" + +CfgCategory* lastCreatedCfgCategory = nullptr; +extern CfgMain* lastCreatedCfgMain; + +CfgCategory::CfgCategory(const CfgCategory &other) : + QObject(), name(other.name), title(other.title), persistable(other.persistable), childs(other.childs) +{ + lastCreatedCfgCategory = this; + lastCreatedCfgMain->childs[name] = this; + cfgParent = lastCreatedCfgMain; + for (CfgEntry* entry : childs) + entry->parent = this; +} + +CfgCategory::CfgCategory(const QString &name, const QString &title) : + name(name), title(title) +{ + this->persistable = lastCreatedCfgMain->persistable; + lastCreatedCfgCategory = this; + cfgParent = lastCreatedCfgMain; + lastCreatedCfgMain->childs[name] = this; +} + +QString CfgCategory::toString() const +{ + return name; +} + +QHash &CfgCategory::getEntries() +{ + return childs; +} + +void CfgCategory::reset() +{ + for (CfgEntry* entry : childs) + entry->reset(); +} + +void CfgCategory::savepoint(bool transaction) +{ + for (CfgEntry* entry : childs) + entry->savepoint(transaction); +} + +void CfgCategory::restore() +{ + for (CfgEntry* entry : childs) + entry->restore(); +} + +void CfgCategory::release() +{ + for (CfgEntry* entry : childs) + entry->release(); +} + +void CfgCategory::commit() +{ + release(); +} + +void CfgCategory::rollback() +{ + rollback(); +} + +void CfgCategory::begin() +{ + savepoint(true); +} + +QString CfgCategory::getTitle() const +{ + return title; +} + +CfgMain*CfgCategory::getMain() const +{ + return cfgParent; +} + +CfgCategory::operator CfgCategory *() +{ + return this; +} + +void CfgCategory::handleEntryChanged() +{ + emit changed(dynamic_cast(sender())); +} + +CfgCategory::operator QString() const +{ + return name; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.h b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.h new file mode 100644 index 0000000..2a6ccaf --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.h @@ -0,0 +1,53 @@ +#ifndef CFGCATEGORY_H +#define CFGCATEGORY_H + +#include "coreSQLiteStudio_global.h" +#include +#include +#include +#include + +class CfgEntry; +class CfgMain; + +class API_EXPORT CfgCategory : public QObject +{ + Q_OBJECT + + friend class CfgEntry; + + public: + CfgCategory(const CfgCategory& other); + CfgCategory(const QString& name, const QString& title); + + QString toString() const; + operator QString() const; + QHash& getEntries(); + void reset(); + void savepoint(bool transaction = false); + void restore(); + void release(); + void commit(); + void rollback(); + void begin(); + QString getTitle() const; + CfgMain* getMain() const; + operator CfgCategory*(); + + private: + QString name; + QString title; + CfgMain* cfgParent = nullptr; + bool persistable = true; + QHash childs; + + private slots: + void handleEntryChanged(); + + signals: + void changed(CfgEntry* entry); +}; + +Q_DECLARE_METATYPE(CfgCategory*) + +#endif // CFGCATEGORY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.cpp b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.cpp new file mode 100644 index 0000000..6a5f6a4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.cpp @@ -0,0 +1,189 @@ +#include "cfgentry.h" +#include "config_builder/cfgmain.h" +#include "config_builder/cfgcategory.h" +#include "services/config.h" +#include + +extern CfgCategory* lastCreatedCfgCategory; + +CfgEntry::CfgEntry(const CfgEntry& other) : + QObject(), persistable(other.persistable), parent(other.parent), name(other.name), defValue(other.defValue), + title(other.title), defValueFunc(other.defValueFunc) +{ + connect(this, SIGNAL(changed(QVariant)), parent, SLOT(handleEntryChanged())); +} + +CfgEntry::CfgEntry(const QString &name, const QVariant &defValue, const QString &title) : + QObject(), name(name), defValue(defValue), title(title) +{ + if (lastCreatedCfgCategory == nullptr) + { + qCritical() << "No last created category while creating CfgEntry!"; + return; + } + + parent = lastCreatedCfgCategory; + persistable = parent->persistable; + parent->childs[name] = this; + connect(this, SIGNAL(changed(QVariant)), parent, SLOT(handleEntryChanged())); +} + +CfgEntry::~CfgEntry() +{ +} + +QVariant CfgEntry::get() const +{ + if (cached) + return cachedValue; + + QVariant cfgVal; + if (persistable) + cfgVal = CFG->get(parent->toString(), name); + + cachedValue = cfgVal; + cached = true; + if (!persistable || !cfgVal.isValid()) + { + if (defValueFunc) + cachedValue = (*defValueFunc)(); + else + cachedValue = defValue; + + return cachedValue; + } + + return cfgVal; +} + +QVariant CfgEntry::getDefultValue() const +{ + if (defValueFunc) + return (*defValueFunc)(); + else + return defValue; +} + +void CfgEntry::set(const QVariant &value) +{ + bool doPersist = persistable && !transaction; + bool wasChanged = (value != cachedValue); + + if (doPersist && wasChanged) + CFG->set(parent->toString(), name, value); + + if (wasChanged) + cachedValue = value; + + cached = true; + + if (wasChanged) + emit changed(value); + + if (doPersist) + emit persisted(value); +} + +void CfgEntry::defineDefaultValueFunction(CfgEntry::DefaultValueProviderFunc func) +{ + defValueFunc = func; +} + +QString CfgEntry::getFullKey() const +{ + return parent->toString()+"."+name; +} + +QString CfgEntry::getTitle() const +{ + return title; +} + +void CfgEntry::reset() +{ + set(getDefultValue()); +} + +bool CfgEntry::isPersistable() const +{ + return persistable; +} + +bool CfgEntry::isPersisted() const +{ + if (persistable) + return !CFG->get(parent->toString(), name).isNull(); + + return false; +} + +void CfgEntry::savepoint(bool transaction) +{ + backup = get(); + this->transaction = transaction; +} + +void CfgEntry::begin() +{ + savepoint(true); +} + +void CfgEntry::restore() +{ + cachedValue = backup; + cached = true; + transaction = false; +} + +void CfgEntry::release() +{ + backup.clear(); + if (transaction) + { + transaction = false; + if (cached) + { + QVariant valueToSet = cachedValue; + cachedValue = QVariant(); + cached = false; + set(valueToSet); + } + } + +} + +void CfgEntry::commit() +{ + if (!transaction) + return; + + release(); +} + +void CfgEntry::rollback() +{ + if (!transaction) + return; + + restore(); +} + +CfgCategory* CfgEntry::getCategory() const +{ + return parent; +} + +CfgMain* CfgEntry::getMain() const +{ + return parent->getMain(); +} + +CfgEntry::operator CfgEntry*() +{ + return this; +} + +CfgEntry::operator QString() const +{ + return name; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.h b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.h new file mode 100644 index 0000000..92b2a5f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.h @@ -0,0 +1,123 @@ +#ifndef CFGENTRY_H +#define CFGENTRY_H + +#include "coreSQLiteStudio_global.h" +#include +#include +#include + +class CfgCategory; +class CfgMain; + +class API_EXPORT CfgEntry : public QObject +{ + Q_OBJECT + + friend class CfgCategory; + + public: + typedef QVariant (*DefaultValueProviderFunc)(); + + explicit CfgEntry(const CfgEntry& other); + CfgEntry(const QString& name, const QVariant& defValue, const QString& title); + virtual ~CfgEntry(); + + QVariant get() const; + QVariant getDefultValue() const; + void set(const QVariant& value); + operator QString() const; + void defineDefaultValueFunction(DefaultValueProviderFunc func); + QString getFullKey() const; + QString getTitle() const; + void reset(); + bool isPersistable() const; + bool isPersisted() const; + void savepoint(bool transaction = false); + void begin(); + void restore(); + void release(); + void commit(); + void rollback(); + CfgCategory* getCategory() const; + CfgMain* getMain() const; + + /** + * @brief operator CfgEntry * + * + * Allows implict casting from value object into pointer. It simply returns "this". + * It's useful to use config objects directly in QObject::connect() arguments, + * cause it accepts pointers, not values, but CfgEntry is usually accessed by value. + */ + operator CfgEntry*(); + + protected: + bool persistable = true; + CfgCategory* parent = nullptr; + QString name; + QVariant defValue; + QString title; + QVariant backup; + bool transaction = false; + mutable bool cached = false; + mutable QVariant cachedValue; + DefaultValueProviderFunc defValueFunc = nullptr; + + signals: + void changed(const QVariant& newValue); + void persisted(const QVariant& newValue); +}; + +template +class CfgTypedEntry : public CfgEntry +{ + public: + CfgTypedEntry(const QString& name, DefaultValueProviderFunc func, const QString& title) : + CfgEntry(name, QVariant(), title) + { + defineDefaultValueFunction(func); + } + + CfgTypedEntry(const QString& name, const T& defValue, const QString& title) : + CfgEntry(name, QVariant::fromValue(defValue), title) {} + + CfgTypedEntry(const QString& name, DefaultValueProviderFunc func) : + CfgTypedEntry(name, func, QString()) {} + + CfgTypedEntry(const QString& name, const T& defValue, bool persistable) : + CfgTypedEntry(name, defValue, QString()) + { + this->persistable = persistable; + } + + CfgTypedEntry(const QString& name, DefaultValueProviderFunc func, bool persistable) : + CfgTypedEntry(name, func, QString()) + { + this->persistable = persistable; + } + + CfgTypedEntry(const QString& name, const T& defValue) : + CfgTypedEntry(name, defValue, QString()) {} + + CfgTypedEntry(const QString& name) : + CfgEntry(name, QVariant(), QString()) {} + + CfgTypedEntry(const CfgTypedEntry& other) : + CfgEntry(other) {} + + T get() + { + QVariant v = CfgEntry::get(); + return v.value(); + } + + void set(const T& value) + { + CfgEntry::set(QVariant::fromValue(value)); + } +}; + +typedef CfgTypedEntry CfgStringEntry; + +Q_DECLARE_METATYPE(CfgEntry*) + +#endif // CFGENTRY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.cpp b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.cpp new file mode 100644 index 0000000..7e554a8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.cpp @@ -0,0 +1,28 @@ +#include "cfglazyinitializer.h" +#include "common/unused.h" + +QList* CfgLazyInitializer::instances = nullptr; + +CfgLazyInitializer::CfgLazyInitializer(std::function initFunc, const char *name) : + initFunc(initFunc) +{ + UNUSED(name); + if (!instances) + instances = new QList(); + + *instances << this; +} + +void CfgLazyInitializer::init() +{ + if (!instances) + instances = new QList(); + + for (CfgLazyInitializer* initializer : *instances) + initializer->doInitialize(); +} + +void CfgLazyInitializer::doInitialize() +{ + initFunc(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.h b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.h new file mode 100644 index 0000000..d97edc2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.h @@ -0,0 +1,23 @@ +#ifndef CFGLAZYINITIALIZER_H +#define CFGLAZYINITIALIZER_H + +#include "coreSQLiteStudio_global.h" +#include +#include + +class API_EXPORT CfgLazyInitializer +{ + public: + CfgLazyInitializer(std::function initFunc, const char* name); + + static void init(); + + private: + void doInitialize(); + + std::function initFunc; + + static QList* instances; +}; + +#endif // CFGLAZYINITIALIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.cpp b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.cpp new file mode 100644 index 0000000..72fc0d0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.cpp @@ -0,0 +1,120 @@ +#include "cfgmain.h" +#include "config_builder/cfgcategory.h" +#include "config_builder/cfgentry.h" + +CfgMain* lastCreatedCfgMain = nullptr; +QList* CfgMain::instances = nullptr; + +CfgMain::CfgMain(const QString& name, bool persistable, const char *metaName, const QString &title) : + name(name), metaName(metaName), title(title), persistable(persistable) +{ + lastCreatedCfgMain = this; + + if (!instances) + instances = new QList(); + + *instances << this; +} + +CfgMain::~CfgMain() +{ + if (!instances) + instances = new QList(); + + instances->removeOne(this); +} + +void CfgMain::staticInit() +{ + qRegisterMetaType("CfgMain*"); + qRegisterMetaType("CfgCategory*"); + qRegisterMetaType("CfgEntry*"); +} + +QList CfgMain::getInstances() +{ + if (!instances) + instances = new QList(); + + return *instances; +} + +QList CfgMain::getPersistableInstances() +{ + QList list; + for (CfgMain* main : getInstances()) + { + if (main->isPersistable()) + list << main; + } + return list; +} + +QHash &CfgMain::getCategories() +{ + return childs; +} + +void CfgMain::reset() +{ + for (CfgCategory* ctg : childs) + ctg->reset(); +} + +void CfgMain::savepoint(bool transaction) +{ + for (CfgCategory* ctg : childs) + ctg->savepoint(transaction); +} + +void CfgMain::restore() +{ + for (CfgCategory* ctg : childs) + ctg->restore(); +} + +void CfgMain::release() +{ + for (CfgCategory* ctg : childs) + ctg->release(); +} + +void CfgMain::begin() +{ + savepoint(true); +} + +void CfgMain::commit() +{ + release(); +} + +void CfgMain::rollback() +{ + restore(); +} + +bool CfgMain::isPersistable() const +{ + return persistable; +} + +QString CfgMain::getName() const +{ + return name; +} + +const char *CfgMain::getMetaName() const +{ + return metaName; +} + +QString CfgMain::getTitle() const +{ + return title; +} + +CfgMain::operator CfgMain*() +{ + return this; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.h b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.h new file mode 100644 index 0000000..bc9490d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.h @@ -0,0 +1,51 @@ +#ifndef CFGMAIN_H +#define CFGMAIN_H + +#include "coreSQLiteStudio_global.h" +#include +#include +#include +#include + +class CfgCategory; + +class API_EXPORT CfgMain +{ + friend class CfgCategory; + + public: + CfgMain(const QString& name, bool persistable, const char* metaName, const QString& title); + ~CfgMain(); + + static void staticInit(); + static QList getInstances(); + static QList getPersistableInstances(); + + QHash& getCategories(); + void reset(); + void savepoint(bool transaction = false); + void restore(); + void release(); + void begin(); + void commit(); + void rollback(); + + bool isPersistable() const; + QString getName() const; + const char* getMetaName() const; + QString getTitle() const; + operator CfgMain*(); + + private: + QString name; + const char* metaName; + QString title; + bool persistable = true; + QHash childs; + + static QList* instances; +}; + +Q_DECLARE_METATYPE(CfgMain*) + +#endif // CFGMAIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro new file mode 100644 index 0000000..6aa14e2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro @@ -0,0 +1,421 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-02-28T23:00:28 +# +#------------------------------------------------- + +include($$PWD/../dirs.pri) +include($$PWD/../utils.pri) + +OBJECTS_DIR = $$OBJECTS_DIR/coreSQLiteStudio +MOC_DIR = $$MOC_DIR/coreSQLiteStudio +UI_DIR = $$UI_DIR/coreSQLiteStudio + +QT -= gui +QT += script network + +TARGET = coreSQLiteStudio +TEMPLATE = lib + +win32 { + LIBS += -lpsapi $$PWD/../../../lib/libquazip.a +} + +linux: { + DEFINES += SYS_PLUGINS_DIR=/usr/lib/sqlitestudio + portable: { + DESTDIR = $$DESTDIR/lib + } +} + +macx: { + out_file = $$DESTDIR/lib $$TARGET .dylib + QMAKE_POST_LINK += install_name_tool -change libsqlite3.dylib @loader_path/../Frameworks/libsqlite3.dylib $$join(out_file) +} + +LIBS += -lsqlite3 + +DEFINES += CORESQLITESTUDIO_LIBRARY + +portable { + DEFINES += PORTABLE_CONFIG +} + +CONFIG += c++11 +QMAKE_CXXFLAGS += -pedantic + +SOURCES += sqlitestudio.cpp \ + returncode.cpp \ + services/config.cpp \ + common/nulldevice.cpp \ + parser/lexer_low_lev.cpp \ + common/utils.cpp \ + parser/keywords.cpp \ + common/utils_sql.cpp \ + parser/token.cpp \ + parser/lexer.cpp \ + parser/sqlite3_parse.cpp \ + parser/parsercontext.cpp \ + parser/parser.cpp \ + parser/sqlite2_parse.cpp \ + parser/ast/sqlitestatement.cpp \ + parser/ast/sqlitequery.cpp \ + parser/ast/sqlitealtertable.cpp \ + parser/ast/sqliteanalyze.cpp \ + parser/ast/sqlitebegintrans.cpp \ + parser/ast/sqlitecommittrans.cpp \ + parser/ast/sqlitecreateindex.cpp \ + parser/ast/sqlitecreatetable.cpp \ + parser/ast/sqlitecreatetrigger.cpp \ + parser/ast/sqlitecreateview.cpp \ + parser/ast/sqlitecreatevirtualtable.cpp \ + parser/ast/sqlitedelete.cpp \ + parser/ast/sqlitedetach.cpp \ + parser/ast/sqlitedroptable.cpp \ + parser/ast/sqlitedroptrigger.cpp \ + parser/ast/sqlitedropindex.cpp \ + parser/ast/sqlitedropview.cpp \ + parser/ast/sqliteinsert.cpp \ + parser/ast/sqlitepragma.cpp \ + parser/ast/sqlitereindex.cpp \ + parser/ast/sqlitesavepoint.cpp \ + parser/ast/sqliterelease.cpp \ + parser/ast/sqliterollback.cpp \ + parser/ast/sqliteselect.cpp \ + parser/ast/sqliteupdate.cpp \ + parser/ast/sqlitevacuum.cpp \ + parser/ast/sqlitecopy.cpp \ + parser/ast/sqliteemptyquery.cpp \ + parser/parser_helper_stubs.cpp \ + parser/ast/sqliteexpr.cpp \ + parser/ast/sqliteforeignkey.cpp \ + parser/ast/sqliteindexedcolumn.cpp \ + parser/ast/sqlitecolumntype.cpp \ + parser/ast/sqliteconflictalgo.cpp \ + parser/ast/sqlitesortorder.cpp \ + parser/ast/sqliteraise.cpp \ + parser/ast/sqliteorderby.cpp \ + parser/ast/sqlitelimit.cpp \ + parser/ast/sqliteattach.cpp \ + parser/parsererror.cpp \ + selectresolver.cpp \ + schemaresolver.cpp \ + parser/ast/sqlitequerytype.cpp \ + db/db.cpp \ + services/dbmanager.cpp \ + db/sqlresultsrow.cpp \ + db/asyncqueryrunner.cpp \ + completionhelper.cpp \ + completioncomparer.cpp \ + db/queryexecutor.cpp \ + qio.cpp \ + plugins/pluginsymbolresolver.cpp \ + db/sqlerrorresults.cpp \ + db/queryexecutorsteps/queryexecutorstep.cpp \ + db/queryexecutorsteps/queryexecutorcountresults.cpp \ + db/queryexecutorsteps/queryexecutorparsequery.cpp \ + db/queryexecutorsteps/queryexecutorexecute.cpp \ + db/queryexecutorsteps/queryexecutorattaches.cpp \ + db/queryexecutorsteps/queryexecutoraddrowids.cpp \ + db/queryexecutorsteps/queryexecutorlimit.cpp \ + db/queryexecutorsteps/queryexecutorcolumns.cpp \ + db/queryexecutorsteps/queryexecutorcellsize.cpp \ + db/queryexecutorsteps/queryexecutororder.cpp \ + db/sqlerrorcodes.cpp \ + common/readwritelocker.cpp \ + db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp \ + csvformat.cpp \ + csvserializer.cpp \ + db/queryexecutorsteps/queryexecutordatasources.cpp \ + expectedtoken.cpp \ + sqlhistorymodel.cpp \ + db/queryexecutorsteps/queryexecutorexplainmode.cpp \ + services/notifymanager.cpp \ + parser/statementtokenbuilder.cpp \ + parser/ast/sqlitedeferrable.cpp \ + tablemodifier.cpp \ + db/chainexecutor.cpp \ + db/queryexecutorsteps/queryexecutorreplaceviews.cpp \ + services/codeformatter.cpp \ + viewmodifier.cpp \ + log.cpp \ + plugins/plugintype.cpp \ + plugins/genericplugin.cpp \ + common/memoryusage.cpp \ + ddlhistorymodel.cpp \ + datatype.cpp \ + common/table.cpp \ + common/column.cpp \ + dbattacher.cpp \ + services/functionmanager.cpp \ + plugins/scriptingqt.cpp \ + services/impl/configimpl.cpp \ + services/impl/dbmanagerimpl.cpp \ + db/abstractdb.cpp \ + services/impl/functionmanagerimpl.cpp \ + services/impl/pluginmanagerimpl.cpp \ + impl/dbattacherimpl.cpp \ + db/dbsqlite3.cpp \ + plugins/dbpluginsqlite3.cpp \ + parser/ast/sqlitewith.cpp \ + services/impl/collationmanagerimpl.cpp \ + services/exportmanager.cpp \ + exportworker.cpp \ + plugins/scriptingsql.cpp \ + db/queryexecutorsteps/queryexecutordetectschemaalter.cpp \ + querymodel.cpp \ + plugins/genericexportplugin.cpp \ + dbobjectorganizer.cpp \ + db/attachguard.cpp \ + db/invaliddb.cpp \ + dbversionconverter.cpp \ + diff/diff_match_patch.cpp \ + db/sqlquery.cpp \ + db/queryexecutorsteps/queryexecutorvaluesmode.cpp \ + services/importmanager.cpp \ + importworker.cpp \ + services/populatemanager.cpp \ + pluginservicebase.cpp \ + populateworker.cpp \ + plugins/populatesequence.cpp \ + plugins/populaterandom.cpp \ + plugins/populaterandomtext.cpp \ + plugins/populateconstant.cpp \ + plugins/populatedictionary.cpp \ + plugins/populatescript.cpp \ + plugins/builtinplugin.cpp \ + plugins/scriptingqtdbproxy.cpp \ + plugins/sqlformatterplugin.cpp \ + services/bugreporter.cpp \ + services/updatemanager.cpp \ + config_builder/cfgmain.cpp \ + config_builder/cfgcategory.cpp \ + config_builder/cfgentry.cpp \ + config_builder/cfglazyinitializer.cpp \ + committable.cpp \ + services/extralicensemanager.cpp \ + tsvserializer.cpp \ + rsa/BigInt.cpp \ + rsa/Key.cpp \ + rsa/KeyPair.cpp \ + rsa/PrimeGenerator.cpp \ + rsa/RSA.cpp + +HEADERS += sqlitestudio.h\ + coreSQLiteStudio_global.h \ + returncode.h \ + services/config.h \ + common/nulldevice.h \ + parser/lexer_low_lev.h \ + common/utils.h \ + parser/keywords.h \ + parser/token.h \ + common/utils_sql.h \ + parser/lexer.h \ + parser/sqlite3_parse.h \ + parser/parsercontext.h \ + parser/parser.h \ + parser/sqlite2_parse.h \ + parser/ast/sqlitestatement.h \ + parser/ast/sqlitequery.h \ + parser/ast/sqlitealtertable.h \ + parser/ast/sqliteanalyze.h \ + parser/ast/sqlitebegintrans.h \ + parser/ast/sqlitecommittrans.h \ + parser/ast/sqlitecreateindex.h \ + parser/ast/sqlitecreatetable.h \ + parser/ast/sqlitecreatetrigger.h \ + parser/ast/sqlitecreateview.h \ + parser/ast/sqlitecreatevirtualtable.h \ + parser/ast/sqlitedelete.h \ + parser/ast/sqlitedetach.h \ + parser/ast/sqlitedroptable.h \ + parser/ast/sqlitedroptrigger.h \ + parser/ast/sqlitedropindex.h \ + parser/ast/sqlitedropview.h \ + parser/ast/sqliteinsert.h \ + parser/ast/sqlitepragma.h \ + parser/ast/sqlitereindex.h \ + parser/ast/sqlitesavepoint.h \ + parser/ast/sqliterelease.h \ + parser/ast/sqliterollback.h \ + parser/ast/sqliteselect.h \ + parser/ast/sqliteupdate.h \ + parser/ast/sqlitevacuum.h \ + parser/ast/sqlitecopy.h \ + parser/ast/sqlitequerytype.h \ + parser/ast/sqliteemptyquery.h \ + parser/parser_helper_stubs.h \ + parser/ast/sqliteconflictalgo.h \ + parser/ast/sqliteexpr.h \ + parser/ast/sqliteforeignkey.h \ + parser/ast/sqliteindexedcolumn.h \ + parser/ast/sqlitecolumntype.h \ + parser/ast/sqlitesortorder.h \ + parser/ast/sqlitedeferrable.h \ + parser/ast/sqliteraise.h \ + parser/ast/sqliteorderby.h \ + parser/ast/sqlitelimit.h \ + parser/ast/sqliteattach.h \ + parser/parsererror.h \ + common/objectpool.h \ + selectresolver.h \ + schemaresolver.h \ + dialect.h \ + db/db.h \ + services/dbmanager.h \ + db/sqlresultsrow.h \ + db/asyncqueryrunner.h \ + completionhelper.h \ + expectedtoken.h \ + completioncomparer.h \ + plugins/dbplugin.h \ + services/pluginmanager.h \ + db/queryexecutor.h \ + qio.h \ + db/dbpluginoption.h \ + common/global.h \ + parser/ast/sqlitetablerelatedddl.h \ + plugins/pluginsymbolresolver.h \ + db/sqlerrorresults.h \ + db/sqlerrorcodes.h \ + db/queryexecutorsteps/queryexecutorstep.h \ + db/queryexecutorsteps/queryexecutorcountresults.h \ + db/queryexecutorsteps/queryexecutorparsequery.h \ + db/queryexecutorsteps/queryexecutorexecute.h \ + db/queryexecutorsteps/queryexecutorattaches.h \ + db/queryexecutorsteps/queryexecutoraddrowids.h \ + db/queryexecutorsteps/queryexecutorlimit.h \ + db/queryexecutorsteps/queryexecutorcolumns.h \ + db/queryexecutorsteps/queryexecutorcellsize.h \ + common/unused.h \ + db/queryexecutorsteps/queryexecutororder.h \ + common/readwritelocker.h \ + db/queryexecutorsteps/queryexecutorwrapdistinctresults.h \ + csvformat.h \ + csvserializer.h \ + db/queryexecutorsteps/queryexecutordatasources.h \ + sqlhistorymodel.h \ + db/queryexecutorsteps/queryexecutorexplainmode.h \ + services/notifymanager.h \ + parser/statementtokenbuilder.h \ + tablemodifier.h \ + db/chainexecutor.h \ + db/queryexecutorsteps/queryexecutorreplaceviews.h \ + plugins/sqlformatterplugin.h \ + services/codeformatter.h \ + viewmodifier.h \ + log.h \ + plugins/plugintype.h \ + plugins/plugin.h \ + plugins/genericplugin.h \ + common/memoryusage.h \ + ddlhistorymodel.h \ + datatype.h \ + plugins/generalpurposeplugin.h \ + common/table.h \ + common/column.h \ + common/bihash.h \ + common/strhash.h \ + dbattacher.h \ + common/bistrhash.h \ + services/functionmanager.h \ + common/sortedhash.h \ + plugins/scriptingplugin.h \ + plugins/scriptingqt.h \ + services/impl/configimpl.h \ + services/impl/dbmanagerimpl.h \ + db/abstractdb.h \ + services/impl/functionmanagerimpl.h \ + services/impl/pluginmanagerimpl.h \ + impl/dbattacherimpl.h \ + db/abstractdb3.h \ + db/dbsqlite3.h \ + plugins/dbpluginsqlite3.h \ + db/abstractdb2.h \ + parser/ast/sqlitewith.h \ + services/collationmanager.h \ + services/impl/collationmanagerimpl.h \ + plugins/exportplugin.h \ + config_builder.h \ + services/exportmanager.h \ + exportworker.h \ + plugins/scriptingsql.h \ + db/queryexecutorsteps/queryexecutordetectschemaalter.h \ + querymodel.h \ + plugins/genericexportplugin.h \ + dbobjectorganizer.h \ + db/attachguard.h \ + interruptable.h \ + db/invaliddb.h \ + dbversionconverter.h \ + diff/diff_match_patch.h \ + db/sqlquery.h \ + dbobjecttype.h \ + db/queryexecutorsteps/queryexecutorvaluesmode.h \ + plugins/importplugin.h \ + services/importmanager.h \ + importworker.h \ + plugins/populateplugin.h \ + services/populatemanager.h \ + pluginservicebase.h \ + populateworker.h \ + plugins/populatesequence.h \ + plugins/populaterandom.h \ + plugins/populaterandomtext.h \ + plugins/populateconstant.h \ + plugins/populatedictionary.h \ + plugins/populatescript.h \ + plugins/builtinplugin.h \ + plugins/scriptingqtdbproxy.h \ + plugins/codeformatterplugin.h \ + services/bugreporter.h \ + services/updatemanager.h \ + config_builder/cfgmain.h \ + config_builder/cfgcategory.h \ + config_builder/cfgentry.h \ + config_builder/cfglazyinitializer.h \ + plugins/confignotifiableplugin.h \ + committable.h \ + plugins/uiconfiguredplugin.h \ + services/extralicensemanager.h \ + db/stdsqlite3driver.h \ + tsvserializer.h \ + rsa/BigInt.h \ + rsa/Key.h \ + rsa/KeyPair.h \ + rsa/PrimeGenerator.h \ + rsa/RSA.h + +unix:!symbian { + maemo5 { + target.path = /opt/usr/lib + } else { + target.path = /usr/lib + } + INSTALLS += target +} + +OTHER_FILES += \ + parser/lempar.c \ + parser/sqlite3_parse.y \ + parser/sqlite2_parse.y \ + parser/run_lemon.sh \ + TODO.txt \ + licenses/fugue_icons.txt \ + licenses/qhexedit.txt \ + licenses/sqlitestudio_license.txt \ + licenses/lgpl.txt \ + licenses/diff_match.txt \ + licenses/gpl.txt + +FORMS += \ + plugins/populatesequence.ui \ + plugins/populaterandom.ui \ + plugins/populaterandomtext.ui \ + plugins/populateconstant.ui \ + plugins/populatedictionary.ui \ + plugins/populatescript.ui + +RESOURCES += \ + coresqlitestudio.qrc diff --git a/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio_global.h b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio_global.h new file mode 100644 index 0000000..402246f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio_global.h @@ -0,0 +1,19 @@ +#ifndef CORESQLITESTUDIO_GLOBAL_H +#define CORESQLITESTUDIO_GLOBAL_H + +#include + +#if defined(CORESQLITESTUDIO_LIBRARY) +# define API_EXPORT Q_DECL_EXPORT +#else +# define API_EXPORT +//# define API_EXPORT Q_DECL_IMPORT +#endif + +#ifdef Q_OS_WIN +# define PATH_LIST_SEPARATOR ";" +#else +# define PATH_LIST_SEPARATOR ":" +#endif + +#endif // CORESQLITESTUDIO_GLOBAL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/coresqlitestudio.qrc b/SQLiteStudio3/coreSQLiteStudio/coresqlitestudio.qrc new file mode 100644 index 0000000..2b58106 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/coresqlitestudio.qrc @@ -0,0 +1,21 @@ + + + plugins/populatesequence.ui + plugins/populaterandomtext.ui + plugins/populatedictionary.ui + plugins/populateconstant.ui + plugins/populaterandom.ui + plugins/populatescript.ui + + + plugins/scriptingsql.png + plugins/scriptingqt.png + + + licenses/fugue_icons.txt + licenses/sqlitestudio_license.txt + licenses/lgpl.txt + licenses/diff_match.txt + licenses/gpl.txt + + diff --git a/SQLiteStudio3/coreSQLiteStudio/csvformat.cpp b/SQLiteStudio3/coreSQLiteStudio/csvformat.cpp new file mode 100644 index 0000000..2876b88 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/csvformat.cpp @@ -0,0 +1,13 @@ +#include "csvformat.h" +#include + +const CsvFormat CsvFormat::DEFAULT = {",", "\n"}; + +CsvFormat::CsvFormat() +{ +} + +CsvFormat::CsvFormat(const QString& columnSeparator, const QString& rowSeparator) : + columnSeparator(columnSeparator), rowSeparator(rowSeparator) +{ +} diff --git a/SQLiteStudio3/coreSQLiteStudio/csvformat.h b/SQLiteStudio3/coreSQLiteStudio/csvformat.h new file mode 100644 index 0000000..c569147 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/csvformat.h @@ -0,0 +1,18 @@ +#ifndef CSVFORMAT_H +#define CSVFORMAT_H + +#include "coreSQLiteStudio_global.h" +#include + +struct API_EXPORT CsvFormat +{ + CsvFormat(); + CsvFormat(const QString& columnSeparator, const QString& rowSeparator); + + QString columnSeparator; + QString rowSeparator; + + static const CsvFormat DEFAULT; +}; + +#endif // CSVFORMAT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp b/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp new file mode 100644 index 0000000..97cc739 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp @@ -0,0 +1,94 @@ +#include "csvserializer.h" +#include + +// TODO write unit tests for CsvSerializer + +QString CsvSerializer::serialize(const QList& data, const CsvFormat& format) +{ + QStringList outputRows; + + foreach (const QStringList& dataRow, data) + outputRows << serialize(dataRow, format); + + return outputRows.join(format.rowSeparator); +} + +QString CsvSerializer::serialize(const QStringList& data, const CsvFormat& format) +{ + QString value; + bool hasQuote; + QStringList outputCells; + foreach (const QString& rowValue, data) + { + value = rowValue; + + hasQuote = value.contains("\""); + if (hasQuote) + value.replace("\"", "\"\""); + + if (hasQuote || value.contains(format.columnSeparator) || value.contains(format.rowSeparator)) + value = "\""+value+"\""; + + outputCells << value; + } + + return outputCells.join(format.columnSeparator); +} + +QList CsvSerializer::deserialize(const QString& data, const CsvFormat& format) +{ + QList rows; + QStringList cells; + + int pos = 0; + int lgt = data.length(); + bool quotes = false; + QString field = ""; + QChar c; + + while (pos < lgt) + { + c = data[pos]; + if (!quotes && c == '"' ) + { + quotes = true; + } + else if (quotes && c == '"' ) + { + if (pos + 1 < data.length() && data[pos+1] == '"' ) + { + field += c; + pos++; + } + else + { + quotes = false; + } + } + else if (!quotes && format.columnSeparator.contains(c)) + { + cells << field; + field.clear(); + } + else if (!quotes && format.rowSeparator.contains(c)) + { + cells << field; + rows << cells; + cells.clear(); + field.clear(); + } + else + { + field += c; + } + pos++; + } + + if (field.size() > 0) + cells << field; + + if (cells.size() > 0) + rows << cells; + + return rows; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/csvserializer.h b/SQLiteStudio3/coreSQLiteStudio/csvserializer.h new file mode 100644 index 0000000..3217203 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/csvserializer.h @@ -0,0 +1,15 @@ +#ifndef CSVSERIALIZER_H +#define CSVSERIALIZER_H + +#include "coreSQLiteStudio_global.h" +#include "csvformat.h" + +class API_EXPORT CsvSerializer +{ + public: + static QString serialize(const QList& data, const CsvFormat& format); + static QString serialize(const QStringList& data, const CsvFormat& format); + static QList deserialize(const QString& data, const CsvFormat& format); +}; + +#endif // CSVSERIALIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/datatype.cpp b/SQLiteStudio3/coreSQLiteStudio/datatype.cpp new file mode 100644 index 0000000..613e6dc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/datatype.cpp @@ -0,0 +1,231 @@ +#include "datatype.h" +#include +#include + +QList DataType::values = [=]() -> QList +{ + QList list; + QMetaEnum metaEnum = DataType::staticMetaObject.enumerator(0); + DataType::Enum value; + for (int i = 0; i < metaEnum.keyCount(); i++) + { + value = static_cast(metaEnum.value(i)); + if (value == DataType::unknown) + continue; + + list << value; + } + + return list; +}(); + +const QStringList DataType::names = [=]() -> QStringList +{ + QStringList list; + QMetaEnum metaEnum = DataType::staticMetaObject.enumerator(0); + DataType::Enum value; + for (int i = 0; i < metaEnum.keyCount(); i++) + { + value = static_cast(metaEnum.value(i)); + if (value == DataType::unknown) + continue; + + list << DataType::toString(value); + } + + return list; +}(); + +DataType::DataType() +{ + setEmpty(); +} + +DataType::DataType(const QString& fullTypeString) +{ + static const QRegularExpression + re(R"(" + "^(?[^\)]*)\s*(\((?[\d\.]+)\s*(,\s*(?[\d\.])+\s*)?\))?$" + ")"); + + QRegularExpressionMatch match = re.match(fullTypeString); + if (!match.hasMatch()) + { + setEmpty(); + return; + } + + typeStr = match.captured("type"); + type = fromString(typeStr, Qt::CaseInsensitive); + precision = match.captured("precision"); + scale = match.captured("scale"); +} + +DataType::DataType(const QString& type, const QVariant& scale, const QVariant& precision) +{ + this->type = fromString(type, Qt::CaseInsensitive); + this->typeStr = type; + this->precision = precision; + this->scale = scale; +} + +DataType::DataType(const DataType& other) : + QObject() +{ + operator=(other); +} + +void DataType::setEmpty() +{ + type = ::DataType::unknown; + typeStr = ""; + precision = QVariant(); + scale = QVariant(); +} + +DataType::Enum DataType::getType() const +{ + return type; +} + +void DataType::setType(DataType::Enum value) +{ + type = value; + typeStr = toString(type); +} + +QVariant DataType::getPrecision() const +{ + return precision; +} + +void DataType::setPrecision(const QVariant& value) +{ + precision = value; +} + +QVariant DataType::getScale() const +{ + return scale; +} + +void DataType::setScale(const QVariant& value) +{ + scale = value; +} + +QString DataType::toString() const +{ + return typeStr; +} + +QString DataType::toFullTypeString() const +{ + QString str = typeStr; + if (!precision.isNull()) + { + if (!scale.isNull()) + str += " ("+scale.toString()+", "+precision.toString()+")"; + else + str += " ("+scale.toString()+")"; + } + return str; +} + +bool DataType::isNumeric() +{ + return isNumeric(type); +} + +bool DataType::isBinary() +{ + return isBinary(typeStr); +} + +bool DataType::isNull() +{ + return type == ::DataType::unknown; +} + +bool DataType::isEmpty() +{ + return typeStr.isEmpty(); +} + +DataType& DataType::operator=(const DataType& other) +{ + this->type = other.type; + this->typeStr = other.typeStr; + this->precision = other.precision; + this->scale = scale; + return *this; +} + +QString DataType::toString(DataType::Enum e) +{ + QMetaEnum metaEnum = staticMetaObject.enumerator(0); + const char* key = metaEnum.valueToKey(e); + if (!key) + return QString::null; + + return key; +} + +DataType::Enum DataType::fromString(QString key, Qt::CaseSensitivity cs) +{ + QMetaEnum metaEnum = staticMetaObject.enumerator(0); + + if (cs == Qt::CaseInsensitive) + key = key.toUpper(); + + bool ok; + Enum value = static_cast(metaEnum.keyToValue(key.toLatin1().data(), &ok)); + if (!ok) + return unknown; + + return value; +} + +bool DataType::isNumeric(DataType::Enum e) +{ + switch (e) + { + case BIGINT: + case DECIMAL: + case DOUBLE: + case INTEGER: + case INT: + case NUMERIC: + case REAL: + return true; + case BLOB: + case BOOLEAN: + case CHAR: + case DATE: + case DATETIME: + case NONE: + case STRING: + case TEXT: + case TIME: + case VARCHAR: + case unknown: + break; + } + return false; +} + +bool DataType::isBinary(const QString& type) +{ + static const QStringList binaryTypes = {"BLOB", "CLOB", "LOB"}; + return binaryTypes.contains(type.toUpper()); +} + +QList DataType::getAllTypes() +{ + return values; +} + +QStringList DataType::getAllNames() +{ + return names; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/datatype.h b/SQLiteStudio3/coreSQLiteStudio/datatype.h new file mode 100644 index 0000000..9d8ca4e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/datatype.h @@ -0,0 +1,72 @@ +#ifndef DATATYPE_H +#define DATATYPE_H + +#include "coreSQLiteStudio_global.h" +#include +#include + +class API_EXPORT DataType : public QObject +{ + Q_OBJECT + Q_ENUMS(Enum) + + public: + enum Enum + { + BIGINT, + BLOB, + BOOLEAN, + CHAR, + DATE, + DATETIME, + DECIMAL, + DOUBLE, + INTEGER, + INT, + NONE, + NUMERIC, + REAL, + STRING, + TEXT, + TIME, + VARCHAR, + unknown + }; + + DataType(); + DataType(const QString& fullTypeString); + DataType(const QString& type, const QVariant& scale, const QVariant& precision); + DataType(const DataType& other); + Enum getType() const; + void setType(Enum value); + QVariant getPrecision() const; + void setPrecision(const QVariant& value); + QVariant getScale() const; + void setScale(const QVariant& value); + QString toString() const; + QString toFullTypeString() const; + void setEmpty(); + bool isNumeric(); + bool isBinary(); + bool isNull(); + bool isEmpty(); + DataType& operator=(const DataType& other); + + static QString toString(Enum e); + static Enum fromString(QString key, Qt::CaseSensitivity cs = Qt::CaseSensitive); + static bool isNumeric(Enum e); + static bool isBinary(const QString& type); + static QList getAllTypes(); + static QStringList getAllNames(); + + private: + Enum type = unknown; + QVariant precision; + QVariant scale; + QString typeStr; + + static QList values; + static const QStringList names; +}; + +#endif // DATATYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp new file mode 100644 index 0000000..56275aa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp @@ -0,0 +1,879 @@ +#include "abstractdb.h" +#include "services/dbmanager.h" +#include "common/utils.h" +#include "asyncqueryrunner.h" +#include "sqlresultsrow.h" +#include "common/utils_sql.h" +#include "services/config.h" +#include "sqlerrorresults.h" +#include "sqlerrorcodes.h" +#include "services/notifymanager.h" +#include "log.h" +#include "parser/lexer.h" +#include +#include +#include +#include +#include +#include +#include + +quint32 AbstractDb::asyncId = 1; + +AbstractDb::AbstractDb(const QString& name, const QString& path, const QHash& connOptions) : + name(name), path(path), connOptions(connOptions) +{ +} + +AbstractDb::~AbstractDb() +{ +} + +bool AbstractDb::open() +{ + bool res = isOpen() || openQuiet(); + if (res) + emit connected(); + + return res; +} + +bool AbstractDb::close() +{ + bool deny = false; + emit aboutToDisconnect(deny); + if (deny) + return false; + + bool res = !isOpen() || closeQuiet(); + if (res) + emit disconnected(); + + return res; +} + +bool AbstractDb::openQuiet() +{ + QWriteLocker locker(&dbOperLock); + QWriteLocker connectionLocker(&connectionStateLock); + return openAndSetup(); +} + +bool AbstractDb::closeQuiet() +{ + QWriteLocker locker(&dbOperLock); + QWriteLocker connectionLocker(&connectionStateLock); + interruptExecution(); + bool res = closeInternal(); + clearAttaches(); + registeredFunctions.clear(); + registeredCollations.clear(); + if (FUNCTIONS) // FUNCTIONS is already null when closing db while closing entire app + disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); + + return res; +} + +bool AbstractDb::openForProbing() +{ + QWriteLocker locker(&dbOperLock); + QWriteLocker connectionLocker(&connectionStateLock); + bool res = openInternal(); + if (!res) + return res; + + // Implementation specific initialization + initAfterOpen(); + return res; +} + +void AbstractDb::registerAllFunctions() +{ + for (const RegisteredFunction& regFn : registeredFunctions) + { + if (!deregisterFunction(regFn.name, regFn.argCount)) + qWarning() << "Failed to deregister custom SQL function:" << regFn.name; + } + + registeredFunctions.clear(); + + RegisteredFunction regFn; + for (FunctionManager::ScriptFunction* fnPtr : FUNCTIONS->getScriptFunctionsForDatabase(getName())) + { + regFn.argCount = fnPtr->undefinedArgs ? -1 : fnPtr->arguments.count(); + regFn.name = fnPtr->name; + regFn.type = fnPtr->type; + registerFunction(regFn); + } + + for (FunctionManager::NativeFunction* fnPtr : FUNCTIONS->getAllNativeFunctions()) + { + regFn.argCount = fnPtr->undefinedArgs ? -1 : fnPtr->arguments.count(); + regFn.name = fnPtr->name; + regFn.type = fnPtr->type; + registerFunction(regFn); + } + + disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); + connect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); +} + +void AbstractDb::registerAllCollations() +{ + foreach (const QString& name, registeredCollations) + { + if (!deregisterCollation(name)) + qWarning() << "Failed to deregister custom collation:" << name; + } + + registeredCollations.clear(); + + foreach (const CollationManager::CollationPtr& collPtr, COLLATIONS->getCollationsForDatabase(getName())) + registerCollation(collPtr->name); + + disconnect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations())); + connect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations())); +} + +bool AbstractDb::isOpen() +{ + // We use separate mutex for connection state to avoid situations, when some query is being executed, + // and we cannot check if database is open, which is not invasive method call. + QReadLocker connectionLocker(&connectionStateLock); + return isOpenInternal(); +} + +QString AbstractDb::generateUniqueDbName(bool lock) +{ + if (lock) + { + QReadLocker locker(&dbOperLock); + return generateUniqueDbNameNoLock(); + } + else + { + return generateUniqueDbNameNoLock(); + } +} + +QString AbstractDb::generateUniqueDbNameNoLock() +{ + SqlQueryPtr results = exec("PRAGMA database_list;", Db::Flag::NO_LOCK); + if (results->isError()) + { + qWarning() << "Could not get PRAGMA database_list. Falling back to internal db list. Error was:" << results->getErrorText(); + return generateUniqueName("attached", attachedDbMap.leftValues()); + } + + QStringList existingDatabases; + foreach (SqlResultsRowPtr row, results->getAll()) + existingDatabases << row->value("name").toString(); + + return generateUniqueName("attached", existingDatabases); +} + +ReadWriteLocker::Mode AbstractDb::getLockingMode(const QString &query, Flags flags) +{ + return ReadWriteLocker::getMode(query, getDialect(), flags.testFlag(Flag::NO_LOCK)); +} + +QString AbstractDb::getName() +{ + return name; +} + +QString AbstractDb::getPath() +{ + return path; +} + +quint8 AbstractDb::getVersion() +{ + return version; +} + +Dialect AbstractDb::getDialect() +{ + if (version == 2) + return Dialect::Sqlite2; + else + return Dialect::Sqlite3; +} + +QString AbstractDb::getEncoding() +{ + bool doClose = false; + if (!isOpen()) + { + if (!openQuiet()) + return QString::null; + + doClose = true; + } + QString encoding = exec("PRAGMA encoding;")->getSingleCell().toString(); + if (doClose) + closeQuiet(); + + return encoding; +} + +QHash& AbstractDb::getConnectionOptions() +{ + return connOptions; +} + +void AbstractDb::setName(const QString& value) +{ + if (isOpen()) + { + qWarning() << "Tried to change database's name while the database was open."; + return; + } + name = value; +} + +void AbstractDb::setPath(const QString& value) +{ + if (isOpen()) + { + qWarning() << "Tried to change database's file path while the database was open."; + return; + } + path = value; +} + +void AbstractDb::setConnectionOptions(const QHash& value) +{ + if (isOpen()) + { + qWarning() << "Tried to change database's connection options while the database was open."; + return; + } + connOptions = value; +} + +SqlQueryPtr AbstractDb::exec(const QString& query, AbstractDb::Flags flags) +{ + return exec(query, QList(), flags); +} + +SqlQueryPtr AbstractDb::exec(const QString& query, const QVariant& arg) +{ + return exec(query, {arg}); +} + +SqlQueryPtr AbstractDb::exec(const QString& query, std::initializer_list argList) +{ + return exec(query, QList(argList)); +} + +SqlQueryPtr AbstractDb::exec(const QString &query, std::initializer_list > argMap) +{ + return exec(query, QHash(argMap)); +} + +void AbstractDb::asyncExec(const QString &query, const QList &args, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags) +{ + quint32 asyncId = asyncExec(query, args, flags); + resultHandlers[asyncId] = resultsHandler; +} + +void AbstractDb::asyncExec(const QString &query, const QHash &args, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags) +{ + quint32 asyncId = asyncExec(query, args, flags); + resultHandlers[asyncId] = resultsHandler; +} + +void AbstractDb::asyncExec(const QString &query, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags) +{ + quint32 asyncId = asyncExec(query, flags); + resultHandlers[asyncId] = resultsHandler; +} + +SqlQueryPtr AbstractDb::exec(const QString &query, const QList& args, Flags flags) +{ + return execListArg(query, args, flags); +} + +SqlQueryPtr AbstractDb::exec(const QString& query, const QHash& args, AbstractDb::Flags flags) +{ + return execHashArg(query, args, flags); +} + +SqlQueryPtr AbstractDb::execHashArg(const QString& query, const QHash& args, Flags flags) +{ + if (!isOpenInternal()) + return SqlQueryPtr(new SqlErrorResults(SqlErrorCode::DB_NOT_OPEN, tr("Cannot execute query on closed database."))); + + logSql(this, query, args, flags); + QString newQuery = query; + SqlQueryPtr queryStmt = prepare(newQuery); + queryStmt->setArgs(args); + queryStmt->setFlags(flags); + queryStmt->execute(); + + if (flags.testFlag(Flag::PRELOAD)) + queryStmt->preload(); + + return queryStmt; +} + +SqlQueryPtr AbstractDb::execListArg(const QString& query, const QList& args, Flags flags) +{ + if (!isOpenInternal()) + return SqlQueryPtr(new SqlErrorResults(SqlErrorCode::DB_NOT_OPEN, tr("Cannot execute query on closed database."))); + + logSql(this, query, args, flags); + QString newQuery = query; + SqlQueryPtr queryStmt = prepare(newQuery); + queryStmt->setArgs(args); + queryStmt->setFlags(flags); + queryStmt->execute(); + + if (flags.testFlag(Flag::PRELOAD)) + queryStmt->preload(); + + return queryStmt; +} + +bool AbstractDb::openAndSetup() +{ + bool result = openInternal(); + if (!result) + return result; + + // When this is an internal configuration database + if (connOptions.contains(DB_PURE_INIT)) + return true; + + // Implementation specific initialization + initAfterOpen(); + + // Custom SQL functions + registerAllFunctions(); + + // Custom collations + registerAllCollations(); + + return result; +} + +void AbstractDb::initAfterOpen() +{ +} + +void AbstractDb::checkForDroppedObject(const QString& query) +{ + TokenList tokens = Lexer::tokenize(query, getDialect()); + tokens.trim(Token::OPERATOR, ";"); + if (tokens.size() == 0) + return; + + if (tokens[0]->type != Token::KEYWORD || tokens.first()->value.toUpper() != "DROP") + return; + + tokens.removeFirst(); // remove "DROP" from front + tokens.trimLeft(); // remove whitespaces and comments from front + if (tokens.size() == 0) + { + qWarning() << "Successful execution of DROP, but after removing DROP from front of the query, nothing has left. Original query:" << query; + return; + } + + QString type = tokens.first()->value.toUpper(); + + // Now go to the first ID in the tokens + while (tokens.size() > 0 && tokens.first()->type != Token::OTHER) + tokens.removeFirst(); + + if (tokens.size() == 0) + { + qWarning() << "Successful execution of DROP, but after removing DROP and non-ID tokens from front of the query, nothing has left. Original query:" << query; + return; + } + + QString database = "main"; + QString object; + + if (tokens.size() > 1) + { + database = tokens.first()->value; + object = tokens.last()->value; + } + else + object = tokens.first()->value; + + object = stripObjName(object, getDialect()); + + if (type == "TABLE") + emit dbObjectDeleted(database, object, DbObjectType::TABLE); + else if (type == "INDEX") + emit dbObjectDeleted(database, object, DbObjectType::INDEX); + else if (type == "TRIGGER") + emit dbObjectDeleted(database, object, DbObjectType::TRIGGER); + else if (type == "VIEW") + emit dbObjectDeleted(database, object, DbObjectType::VIEW); + else + qWarning() << "Unknown object type dropped:" << type; +} + +bool AbstractDb::registerCollation(const QString& name) +{ + if (registeredCollations.contains(name)) + { + qCritical() << "Collation" << name << "is already registered!" + << "It should already be deregistered while call to register is being made."; + return false; + } + + if (registerCollationInternal(name)) + { + registeredCollations << name; + return true; + } + + qCritical() << "Could not register collation:" << name; + return false; +} + +bool AbstractDb::deregisterCollation(const QString& name) +{ + if (!registeredCollations.contains(name)) + { + qCritical() << "Collation" << name << "not registered!" + << "It should already registered while call to deregister is being made."; + return false; + } + + if (deregisterCollationInternal(name)) + { + registeredCollations.removeOne(name); + return true; + } + qWarning() << "Could not deregister collation:" << name; + return false; +} + +bool AbstractDb::isCollationRegistered(const QString& name) +{ + return registeredCollations.contains(name); +} + +QHash AbstractDb::getAggregateContext(void* memPtr) +{ + if (!memPtr) + { + qCritical() << "Could not allocate aggregate context."; + return QHash(); + } + + QHash** aggCtxPtr = reinterpret_cast**>(memPtr); + if (!*aggCtxPtr) + *aggCtxPtr = new QHash(); + + return **aggCtxPtr; +} + +void AbstractDb::setAggregateContext(void* memPtr, const QHash& aggregateContext) +{ + if (!memPtr) + { + qCritical() << "Could not extract aggregate context."; + return; + } + + QHash** aggCtxPtr = reinterpret_cast**>(memPtr); + **aggCtxPtr = aggregateContext; +} + +void AbstractDb::releaseAggregateContext(void* memPtr) +{ + if (!memPtr) + { + qCritical() << "Could not release aggregate context."; + return; + } + + QHash** aggCtxPtr = reinterpret_cast**>(memPtr); + delete *aggCtxPtr; +} + +QVariant AbstractDb::evaluateScalar(void* dataPtr, const QList& argList, bool& ok) +{ + if (!dataPtr) + return QVariant(); + + FunctionUserData* userData = reinterpret_cast(dataPtr); + + return FUNCTIONS->evaluateScalar(userData->name, userData->argCount, argList, userData->db, ok); +} + +void AbstractDb::evaluateAggregateStep(void* dataPtr, QHash& aggregateContext, QList argList) +{ + if (!dataPtr) + return; + + FunctionUserData* userData = reinterpret_cast(dataPtr); + + QHash storage = aggregateContext["storage"].toHash(); + if (!aggregateContext.contains("initExecuted")) + { + FUNCTIONS->evaluateAggregateInitial(userData->name, userData->argCount, userData->db, storage); + aggregateContext["initExecuted"] = true; + } + + FUNCTIONS->evaluateAggregateStep(userData->name, userData->argCount, argList, userData->db, storage); + aggregateContext["storage"] = storage; +} + +QVariant AbstractDb::evaluateAggregateFinal(void* dataPtr, QHash& aggregateContext, bool& ok) +{ + if (!dataPtr) + return QVariant(); + + FunctionUserData* userData = reinterpret_cast(dataPtr); + QHash storage = aggregateContext["storage"].toHash(); + + return FUNCTIONS->evaluateAggregateFinal(userData->name, userData->argCount, userData->db, ok, storage); +} + +quint32 AbstractDb::asyncExec(const QString &query, Flags flags) +{ + AsyncQueryRunner* runner = new AsyncQueryRunner(query, QList(), flags); + return asyncExec(runner); +} + +quint32 AbstractDb::asyncExec(const QString& query, const QHash& args, AbstractDb::Flags flags) +{ + AsyncQueryRunner* runner = new AsyncQueryRunner(query, args, flags); + return asyncExec(runner); +} + +quint32 AbstractDb::asyncExec(const QString& query, const QList& args, AbstractDb::Flags flags) +{ + AsyncQueryRunner* runner = new AsyncQueryRunner(query, args, flags); + return asyncExec(runner); +} + +quint32 AbstractDb::asyncExec(AsyncQueryRunner *runner) +{ + quint32 asyncId = generateAsyncId(); + runner->setDb(this); + runner->setAsyncId(asyncId); + + connect(runner, SIGNAL(finished(AsyncQueryRunner*)), + this, SLOT(asyncQueryFinished(AsyncQueryRunner*))); + + QThreadPool::globalInstance()->start(runner); + + return asyncId; +} + +void AbstractDb::asyncQueryFinished(AsyncQueryRunner *runner) +{ + // Extract everything from the runner + SqlQueryPtr results = runner->getResults(); + quint32 asyncId = runner->getAsyncId(); + delete runner; + + if (handleResultInternally(asyncId, results)) + return; + + emit asyncExecFinished(asyncId, results); + + if (isReadable() && isWritable()) + emit idle(); +} + +QString AbstractDb::attach(Db* otherDb, bool silent) +{ + QWriteLocker locker(&dbOperLock); + if (!isOpenInternal()) + return QString::null; + + if (attachedDbMap.containsRight(otherDb)) + { + attachCounter[otherDb]++; + return attachedDbMap.valueByRight(otherDb); + } + + QString attName = generateUniqueDbName(false); + SqlQueryPtr results = exec(getAttachSql(otherDb, attName), Flag::NO_LOCK); + if (results->isError()) + { + if (!silent) + notifyError(tr("Error attaching database %1: %2").arg(otherDb->getName()).arg(results->getErrorText())); + else + qDebug() << QString("Error attaching database %1: %2").arg(otherDb->getName()).arg(results->getErrorText()); + + return QString::null; + } + + attachedDbMap.insert(attName, otherDb); + + emit attached(otherDb); + return attName; +} + +void AbstractDb::detach(Db* otherDb) +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return; + + detachInternal(otherDb); +} + +void AbstractDb::detachInternal(Db* otherDb) +{ + if (!attachedDbMap.containsRight(otherDb)) + return; + + if (attachCounter.contains(otherDb)) + { + attachCounter[otherDb]--; + return; + } + + exec(QString("DETACH %1;").arg(attachedDbMap.valueByRight(otherDb)), Flag::NO_LOCK); + attachedDbMap.removeRight(otherDb); + emit detached(otherDb); +} + +void AbstractDb::clearAttaches() +{ + attachedDbMap.clear(); + attachCounter.clear(); +} + +void AbstractDb::detachAll() +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return; + + foreach (Db* db, attachedDbMap.rightValues()) + detachInternal(db); +} + +const QHash &AbstractDb::getAttachedDatabases() +{ + QReadLocker locker(&dbOperLock); + return attachedDbMap.toInvertedQHash(); +} + +QSet AbstractDb::getAllAttaches() +{ + QReadLocker locker(&dbOperLock); + QSet attaches = attachedDbMap.leftValues().toSet(); + // TODO query database for attached databases and unite them here + return attaches; +} + +QString AbstractDb::getUniqueNewObjectName(const QString &attachedDbName) +{ + QString dbName = getPrefixDb(attachedDbName, getDialect()); + + QSet existingNames; + SqlQueryPtr results = exec(QString("SELECT name FROM %1.sqlite_master").arg(dbName)); + + foreach (SqlResultsRowPtr row, results->getAll()) + existingNames << row->value(0).toString(); + + return randStrNotIn(16, existingNames, false); +} + +QString AbstractDb::getErrorText() +{ + QReadLocker locker(&dbOperLock); + return getErrorTextInternal(); +} + +int AbstractDb::getErrorCode() +{ + QReadLocker locker(&dbOperLock); + return getErrorCodeInternal(); +} + +bool AbstractDb::initAfterCreated() +{ + bool isOpenBefore = isOpen(); + if (!isOpenBefore) + { + if (!openForProbing()) + { + qWarning() << "Could not open database for initAfterCreated(). Database:" << name; + return false; + } + } + + // SQLite version + QVariant value = exec("SELECT sqlite_version()")->getSingleCell(); + version = value.toString().mid(0, 1).toUInt(); + + if (!isOpenBefore) + closeQuiet(); + + return true; +} + +void AbstractDb::setTimeout(int secs) +{ + timeout = secs; +} + +int AbstractDb::getTimeout() const +{ + return timeout; +} + +bool AbstractDb::isValid() const +{ + return true; +} + +QString AbstractDb::getAttachSql(Db* otherDb, const QString& generatedAttachName) +{ + return QString("ATTACH '%1' AS %2;").arg(otherDb->getPath(), generatedAttachName); +} + +quint32 AbstractDb::generateAsyncId() +{ + if (asyncId > 4000000000) + asyncId = 1; + + return asyncId++; +} + +bool AbstractDb::begin() +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return false; + + SqlQueryPtr results = exec("BEGIN;", Flag::NO_LOCK); + if (results->isError()) + { + qCritical() << "Error while starting a transaction: " << results->getErrorCode() << results->getErrorText(); + return false; + } + + return true; +} + +bool AbstractDb::commit() +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return false; + + SqlQueryPtr results = exec("COMMIT;", Flag::NO_LOCK); + if (results->isError()) + { + qCritical() << "Error while commiting a transaction: " << results->getErrorCode() << results->getErrorText(); + return false; + } + + return true; +} + +bool AbstractDb::rollback() +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return false; + + SqlQueryPtr results = exec("ROLLBACK;", Flag::NO_LOCK); + if (results->isError()) + { + qCritical() << "Error while rolling back a transaction: " << results->getErrorCode() << results->getErrorText(); + return false; + } + + return true; +} + +void AbstractDb::interrupt() +{ + // Lock connection state to forbid closing db before interrupt() returns. + // This is required by SQLite. + QWriteLocker locker(&connectionStateLock); + interruptExecution(); +} + +void AbstractDb::asyncInterrupt() +{ + QtConcurrent::run(this, &AbstractDb::interrupt); +} + +bool AbstractDb::isReadable() +{ + bool res = dbOperLock.tryLockForRead(); + if (res) + dbOperLock.unlock(); + + return res; +} + +bool AbstractDb::isWritable() +{ + bool res = dbOperLock.tryLockForWrite(); + if (res) + dbOperLock.unlock(); + + return res; +} + +AttachGuard AbstractDb::guardedAttach(Db* otherDb, bool silent) +{ + QString attachName = attach(otherDb, silent); + return AttachGuard::create(this, otherDb, attachName); +} + +bool AbstractDb::handleResultInternally(quint32 asyncId, SqlQueryPtr results) +{ + if (!resultHandlers.contains(asyncId)) + return false; + + resultHandlers[asyncId](results); + resultHandlers.remove(asyncId); + + return true; +} + +void AbstractDb::registerFunction(const AbstractDb::RegisteredFunction& function) +{ + if (registeredFunctions.contains(function)) + return; // native function was overwritten by script function + + bool successful = false; + switch (function.type) + { + case FunctionManager::ScriptFunction::SCALAR: + successful = registerScalarFunction(function.name, function.argCount); + break; + case FunctionManager::ScriptFunction::AGGREGATE: + successful = registerAggregateFunction(function.name, function.argCount); + break; + } + + if (successful) + registeredFunctions << function; + else + qCritical() << "Could not register SQL function:" << function.name << function.argCount << function.type; +} + +int qHash(const AbstractDb::RegisteredFunction& fn) +{ + return qHash(fn.name) ^ fn.argCount ^ fn.type; +} + +bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2) +{ + return fn1.name == fn2.name && fn1.argCount == fn2.argCount && fn1.type == fn2.type; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h new file mode 100644 index 0000000..89edf03 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h @@ -0,0 +1,485 @@ +#ifndef ABSTRACTDB_H +#define ABSTRACTDB_H + +#include "returncode.h" +#include "sqlquery.h" +#include "dialect.h" +#include "db/db.h" +#include "common/bihash.h" +#include "services/functionmanager.h" +#include "common/readwritelocker.h" +#include "coreSQLiteStudio_global.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class AsyncQueryRunner; + +/** + * @brief Base database logic implementation. + * + * This class implements common base logic for all database implementations. It's still abstract class + * and needs further implementation to be usable. + */ +class API_EXPORT AbstractDb : public Db +{ + Q_OBJECT + + public: + /** + * @brief Initializes database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See below for details. + * + * Connection options are handled individually by the derived database implementation class. + * It can be password for encrypted databases, read-only access flag, etc. + */ + AbstractDb(const QString& name, const QString& path, const QHash& connOptions); + + virtual ~AbstractDb(); + + bool isOpen(); + QString getName(); + QString getPath(); + quint8 getVersion(); + Dialect getDialect(); + QString getEncoding(); + QHash& getConnectionOptions(); + void setName(const QString& value); + void setPath(const QString& value); + void setConnectionOptions(const QHash& value); + SqlQueryPtr exec(const QString& query, const QList &args, Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString& query, const QHash& args, Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString &query, Db::Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString &query, const QVariant &arg); + SqlQueryPtr exec(const QString &query, std::initializer_list argList); + SqlQueryPtr exec(const QString &query, std::initializer_list> argMap); + void asyncExec(const QString& query, const QList& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + void asyncExec(const QString& query, const QHash& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + void asyncExec(const QString& query, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, const QList& args, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, const QHash& args, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, Flags flags = Flag::NONE); + bool begin(); + bool commit(); + bool rollback(); + void interrupt(); + void asyncInterrupt(); + bool isReadable(); + bool isWritable(); + AttachGuard guardedAttach(Db* otherDb, bool silent = false); + QString attach(Db* otherDb, bool silent = false); + void detach(Db* otherDb); + void detachAll(); + const QHash& getAttachedDatabases(); + QSet getAllAttaches(); + QString getUniqueNewObjectName(const QString& attachedDbName = QString()); + QString getErrorText(); + int getErrorCode(); + bool initAfterCreated(); + void setTimeout(int secs); + int getTimeout() const; + bool isValid() const; + + protected: + struct FunctionUserData + { + QString name; + int argCount = 0; + Db* db = nullptr; + }; + + virtual QString getAttachSql(Db* otherDb, const QString& generatedAttachName); + + /** + * @brief Generates unique database name for ATTACH. + * @param lock Defines if the lock on dbOperLock mutex. + * @return Unique database name. + * + * Database name here is the name to be used for ATTACH statement. + * For example it will never be "main" or "temp", as those names are already used. + * It also will never be any name that is currently used for any ATTACH'ed database. + * It respects both manual ATTACH'es (called by user), as well as by attach() calls. + * + * Operations on database are normally locked during name generation, because it involves + * queries to the database about what are currently existing objects. + * The lock can be ommited if the calling method already locked dbOperLock. + */ + QString generateUniqueDbName(bool lock = true); + + /** + * @brief Detaches given database object from this database. + * @param otherDb Other registered database. + * + * This is called from detach() and detachAll(). + */ + void detachInternal(Db* otherDb); + + /** + * @brief Clears attached databases list. + * + * Called by closeQuiet(). Only clears maps and lists regarding attached databases. + * It doesn't call detach(), because closing the database will already detach all databases. + */ + void clearAttaches(); + + /** + * @brief Generated unique ID for asynchronous query execution. + * @return Unique ID. + */ + static quint32 generateAsyncId(); + + /** + * @brief Executes query asynchronously. + * @param runner Prepared object for asynchronous execution. + * @return Asynchronous execution unique ID. + * + * This is called by asyncExec(). Runs prepared runner object (which has all information about the query) + * on separate thread. + */ + quint32 asyncExec(AsyncQueryRunner* runner); + + /** + * @brief Opens the database and calls initial setup. + * @return true on success, false on failure. + * + * Calls openInternal() and if it succeeded, calls initialDbSetup(). + * It's called from openQuiet(). + */ + bool openAndSetup(); + + /** + * @brief Checks if the database connection is open. + * @return true if the connection is open, or false otherwise. + * + * This is called from isOpen(). Implementation should test and return information if the database + * connection is open. A lock on connectionStateLock is already set by the isOpen() method. + */ + virtual bool isOpenInternal() = 0; + + /** + * @brief Interrupts execution of any queries. + * + * Implementation of this method should interrupt any query executions that are currently in progress. + * Typical implementation for SQLite databases will call sqlite_interupt() / sqlite3_interupt(). + */ + virtual void interruptExecution() = 0; + + /** + * @brief Returns error message. + * @return Error string. + * + * This can be either error from last query execution, but also from connection opening problems, etc. + */ + virtual QString getErrorTextInternal() = 0; + + /** + * @brief Returns error code. + * @return Error code. + * + * This can be either error from last query execution, but also from connection opening problems, etc. + */ + virtual int getErrorCodeInternal() = 0; + + /** + * @brief Opens database connection. + * @return true on success, false on failure. + * + * Opens database. Called by open() and openAndSetup(). + */ + virtual bool openInternal() = 0; + + /** + * @brief Closes database connection. + * + * Closes database. Called by open() and openQuiet(). + */ + virtual bool closeInternal() = 0; + + virtual void initAfterOpen(); + + void checkForDroppedObject(const QString& query); + bool registerCollation(const QString& name); + bool deregisterCollation(const QString& name); + bool isCollationRegistered(const QString& name); + + /** + * @brief Registers a collation sequence implementation in the database. + * @param name Name of the collation. + * @return true on success, false on failure. + * + * This should be low-level implementation depended on SQLite driver. + * The general implementation of registerCollation() in this class just keeps track on collations + * registered. + */ + virtual bool registerCollationInternal(const QString& name) = 0; + + /** + * @brief Deregisters previously registered collation from this database. + * @param name Collation name. + * @return true on success, false on failure. + * + * This should be low-level implementation depended on SQLite driver. + * The general implementation of registerCollation() in this class just keeps track on collations + * registered. + */ + virtual bool deregisterCollationInternal(const QString& name) = 0; + + static QHash getAggregateContext(void* memPtr); + static void setAggregateContext(void* memPtr, const QHash& aggregateContext); + static void releaseAggregateContext(void* memPtr); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param dataPtr SQL function user data (defined when registering function). Must be of FunctionUserData* type, or descendant. + * @param argList List of arguments passed to the function. + * @param[out] ok true (default) to indicate successful execution, or false to report an error. + * @return Result returned from the plugin handling function implementation. + * + * This method is aware of the implementation language and the code defined for it, + * so it delegates the execution to the proper plugin handling that language. + * + * This method is called for scalar functions. + */ + static QVariant evaluateScalar(void* dataPtr, const QList& argList, bool& ok); + static void evaluateAggregateStep(void* dataPtr, QHash& aggregateContext, QList argList); + static QVariant evaluateAggregateFinal(void* dataPtr, QHash& aggregateContext, bool& ok); + + /** + * @brief Database name. + * + * It must be unique across all Db instances. Use generateUniqueDbName() to get the unique name + * for new database. It's used as a key for DbManager. + * + * Databases are also presented to the user with this name on UI. + */ + QString name; + + /** + * @brief Path to the database file. + */ + QString path; + + /** + * @brief Connection options. + * + * There are no standard options. Custom DbPlugin implementations may support some options. + */ + QHash connOptions; + + /** + * @brief SQLite version of this database. + * + * This is only a major version number (2 or 3). + */ + quint8 version = 0; + + /** + * @brief Map of databases attached to this database. + * + * It's mapping from ATTACH name to the database object. It contains only attaches + * that were made with attach() calls. + */ + BiHash attachedDbMap; + + /** + * @brief Counter of attaching requrests for each database. + * + * When calling attach() on other Db, it gets its own entry in this mapping. + * If the mapping already exists, its value is incremented. + * Then, when calling detach(), counter is decremented and when it reaches 0, + * the database is actualy detached. + */ + QHash attachCounter; + + /** + * @brief Result handler functions for asynchronous executions. + * + * For each asyncExec() with function pointer in argument there's an entry in this map + * pointing to the function. Keys are asynchronous IDs. + */ + QHash resultHandlers; + + /** + * @brief Database operation lock. + * + * This lock is set whenever any operation on the actual database is performed (i.e. call to + * exec(), interrupt(), open(), close(), generateUniqueDbName(true), attach(), detach(), and others... + * generally anything that does operations on database that must be synchronous). + * + * In case of exec() it can be locked for READ or WRITE (depending on query type), + * because there can be multiple SELECTs and there's nothing wrong with it, + * while for other methods is always lock for WRITE. + */ + QReadWriteLock dbOperLock; + + private: + /** + * @brief Represents single function that is registered in the database. + * + * Registered custom SQL functions are diversed by SQLite by their name, arguments count and their type, + * so this structure has exactly those parameters. + */ + struct RegisteredFunction + { + /** + * @brief Function name. + */ + QString name; + + /** + * @brief Arguments count (-1 for undefined count). + */ + int argCount; + + /** + * @brief Function type. + */ + FunctionManager::ScriptFunction::Type type; + }; + + friend int qHash(const AbstractDb::RegisteredFunction& fn); + friend bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2); + + /** + * @brief Applies execution flags and executes query. + * @param query Query to be executed. + * @param args Query parameters. + * @param flags Query execution flags. + * @return Execution results - either successful or failed. + * + * This is called from both exec() and execNoLock() and is a final step before calling execInternal() + * (the plugin-provided execution). This is where \p flags are interpreted and applied. + */ + SqlQueryPtr execHashArg(const QString& query, const QHash& args, Flags flags); + + /** + * @overload + */ + SqlQueryPtr execListArg(const QString& query, const QList& args, Flags flags); + + /** + * @brief Generates unique database name. + * @return Unique database name. + * + * This is a lock-less variant of generateUniqueDbName(). It is called from that method. + * See generateUniqueDbName() for details. + */ + QString generateUniqueDbNameNoLock(); + + /** + * @brief Provides required locking mode for given query. + * @param query Query to be executed. + * @return Locking mode: READ or WRITE. + * + * Given the query this method analyzes what is the query and provides information if the query + * will do some changes on the database, or not. Then it returns proper locking mode that should + * be used for this query execution. + * + * Query execution methods from this class check if lock mode of the query to be executed isn't + * in conflict with the lock being currently applied on the dbOperLock (if any is applied at the moment). + * + * This method works on a very simple rule. It assumes that queries: SELECT, ANALYZE, EXPLAIN, + * and PRAGMA - are read-only, while all other queries are read-write. + * In case of PRAGMA this is not entirely true, but it's not like using PRAGMA for changing + * some setting would cause database state inconsistency. At least not from perspective of SQLiteStudio. + * + * In case of WITH statement it filters out the "WITH clause" and then checks for SELECT keyword. + */ + ReadWriteLocker::Mode getLockingMode(const QString& query, Db::Flags flags); + + /** + * @brief Handles asynchronous query results with results handler function. + * @param asyncId Asynchronous ID. + * @param results Results from execution. + * @return true if the results were handled, or false if they were not. + * + * This method checks if there is a handler function for given asynchronous ID (in resultHandlers) + * and if there is, then evaluates it and returns true. Otherwise does nothing and returns false. + */ + bool handleResultInternally(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief Registers single custom SQL function. + * @param function Function to register. + * + * If function got registered successfully, it's added to registeredFunctions. + * If there was a function with the same name, argument count and type already registered, + * it will be overwritten (both in SQLite and in registeredFunctions). + */ + void registerFunction(const RegisteredFunction& function); + + /** + * @brief Connection state lock. + * + * It's locked whenever the connection state is changed or tested. + * For open() and close() it's a WRITE lock, for isOpen() it's READ lock. + */ + QReadWriteLock connectionStateLock; + + /** + * @brief Sequence container for generating unique asynchronous IDs. + */ + static quint32 asyncId; + + /** + * @brief Current timeout (in seconds) for waiting for the database to be released from the lock. + * + * See Db::setTimeout() for details. + */ + int timeout = 60; + + /** + * @brief List of all functions currently registered in this database. + */ + QSet registeredFunctions; + + /** + * @brief List of all collations currently registered in this database. + */ + QStringList registeredCollations; + + private slots: + /** + * @brief Handles asynchronous execution results. + * @param runner Container with input and output data of the query. + * + * This is called from the other thread when it finished asynchronous execution. + * It checks if there is any handler function to evaluate it with results + * and if there's not, emits asyncExecFinished() signal. + */ + void asyncQueryFinished(AsyncQueryRunner* runner); + + public slots: + bool open(); + bool close(); + bool openQuiet(); + bool closeQuiet(); + bool openForProbing(); + void registerAllFunctions(); + void registerAllCollations(); +}; + +/** + * @brief Standard function required by QHash. + * @param fn Function to calculate hash for. + * @return Hash value calculated from all members of DbBase::RegisteredFunction. + */ +int qHash(const AbstractDb::RegisteredFunction& fn); + +/** + * @brief Simple comparator operator, compares all members. + * @param other Other function to compare. + * @return true if \p other is equal, false otherwise. + * + * This function had to be declared/defined outside of the DbBase::RegisteredFunction, because QSet/QHash requires this. + */ +bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2); + +#endif // ABSTRACTDB_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h new file mode 100644 index 0000000..e35e038 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h @@ -0,0 +1,882 @@ +#ifndef ABSTRACTDB2_H +#define ABSTRACTDB2_H + +#include "db/abstractdb.h" +#include "parser/lexer.h" +#include "common/utils_sql.h" +#include "common/unused.h" +#include "db/sqlerrorcodes.h" +#include "db/sqlerrorresults.h" +#include +#include +#include +#include + +/** + * @brief Complete implementation of SQLite 2 driver for SQLiteStudio. + * + * Inherit this when implementing Db for SQLite 2. In most cases you will only need + * to create one public constructor, which forwards parameters to the AbstractDb constructor. + * This be sufficient to implement SQLite 2 database plugin. + * Just link it with proper SQLite library. + * + * The template parameter is currently not used for anything specific, so pass any unique type name. + * The best would be to define empty class/structure just for this purpose. + * The parameter is there, so this class becomes a template class. + * We need a template class so we can provide common code base for all SQLite 2 plugins, while the + * code doesn't introduce dependency to SQLite 2 library, until it's used, which is in SQLite 2 plugins. + * + * @see DbQt + */ +template +class AbstractDb2 : public AbstractDb +{ + public: + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See AbstractDb for details. + * + * All values from this constructor are just passed to AbstractDb constructor. + */ + AbstractDb2(const QString& name, const QString& path, const QHash& connOptions); + + ~AbstractDb2(); + + protected: + bool isOpenInternal(); + void interruptExecution(); + QString getErrorTextInternal(); + int getErrorCodeInternal(); + bool openInternal(); + bool closeInternal(); + void initAfterOpen(); + SqlQueryPtr prepare(const QString& query); + QString getTypeLabel(); + bool deregisterFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount); + bool registerAggregateFunction(const QString& name, int argCount); + bool registerCollationInternal(const QString& name); + bool deregisterCollationInternal(const QString& name); + + private: + class Query : public SqlQuery, public Sqlite2ColumnDataTypeHelper + { + public: + class Row : public SqlResultsRow + { + public: + void init(const QStringList& columns, const QList& resultValues); + }; + + Query(AbstractDb2* db, const QString& query); + ~Query(); + + QString getErrorText(); + int getErrorCode(); + QStringList getColumnNames(); + int columnCount(); + qint64 rowsAffected(); + QString finalize(); + + protected: + SqlResultsRowPtr nextInternal(); + bool hasNextInternal(); + bool execInternal(const QList& args); + bool execInternal(const QHash& args); + + private: + int prepareStmt(const QString& processedQuery); + int resetStmt(); + int bindParam(int paramIdx, const QVariant& value); + int fetchNext(); + int fetchFirst(); + void init(int columnsCount, const char** columns); + bool checkDbState(); + void copyErrorFromDb(); + void copyErrorToDb(); + void setError(int code, const QString& msg); + + static QString replaceNamedParams(const QString& query); + + QPointer> db; + sqlite_vm* stmt = nullptr; + int errorCode = SQLITE_OK; + QString errorMessage; + int colCount = -1; + QStringList colNames; + QList nextRowValues; + int affected = 0; + bool rowAvailable = false; + }; + + void cleanUp(); + void resetError(); + QString freeStatement(sqlite_vm* stmt); + + static void storeResult(sqlite_func* func, const QVariant& result, bool ok); + static QList getArgs(int argCount, const char** args); + static void evaluateScalar(sqlite_func* func, int argCount, const char** args); + static void evaluateAggregateStep(sqlite_func* func, int argCount, const char** args); + static void evaluateAggregateFinal(sqlite_func* func); + static void* getContextMemPtr(sqlite_func* func); + static QHash getAggregateContext(sqlite_func* func); + static void setAggregateContext(sqlite_func* func, const QHash& aggregateContext); + static void releaseAggregateContext(sqlite_func* func); + + sqlite* dbHandle = nullptr; + QString dbErrorMessage; + int dbErrorCode = SQLITE_OK; + QList userDataList; + QList queries; +}; + +//------------------------------------------------------------------------------------ +// AbstractDb2 +//------------------------------------------------------------------------------------ + +template +AbstractDb2::AbstractDb2(const QString& name, const QString& path, const QHash& connOptions) : + AbstractDb(name, path, connOptions) +{ +} + +template +AbstractDb2::~AbstractDb2() +{ + if (isOpenInternal()) + closeInternal(); +} + +template +bool AbstractDb2::isOpenInternal() +{ + return dbHandle != nullptr; +} + +template +SqlQueryPtr AbstractDb2::prepare(const QString& query) +{ + return SqlQueryPtr(new Query(this, query)); +} + +template +void AbstractDb2::interruptExecution() +{ + if (!isOpenInternal()) + return; + + sqlite_interrupt(dbHandle); +} + +template +QString AbstractDb2::getErrorTextInternal() +{ + return dbErrorMessage; +} + +template +int AbstractDb2::getErrorCodeInternal() +{ + return dbErrorCode; +} + +template +bool AbstractDb2::openInternal() +{ + resetError(); + sqlite* handle = nullptr; + char* errMsg = nullptr; + handle = sqlite_open(path.toUtf8().constData(), 0, &errMsg); + if (!handle) + { + dbErrorCode = SQLITE_ERROR; + + if (errMsg) + { + dbErrorMessage = tr("Could not open database: %1").arg(QString::fromUtf8(errMsg)); + sqlite_freemem(errMsg); + } + return false; + } + dbHandle = handle; + return true; +} + +template +bool AbstractDb2::closeInternal() +{ + resetError(); + if (!dbHandle) + return false; + + cleanUp(); + + sqlite_close(dbHandle); + dbHandle = nullptr; + return true; +} + +template +void AbstractDb2::initAfterOpen() +{ +} + +template +QString AbstractDb2::getTypeLabel() +{ + return T::label; +} + +template +bool AbstractDb2::deregisterFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + sqlite_create_function(dbHandle, name.toLatin1().data(), argCount, nullptr, nullptr); + sqlite_create_aggregate(dbHandle, name.toLatin1().data(), argCount, nullptr, nullptr, nullptr); + + FunctionUserData* userData = nullptr; + QMutableListIterator it(userDataList); + while (it.hasNext()) + { + userData = it.next(); + if (userData->name == name && userData->argCount == argCount) + { + it.remove(); + delete userData; + } + } + + return true; +} + +template +bool AbstractDb2::registerScalarFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + userDataList << userData; + + int res = sqlite_create_function(dbHandle, name.toUtf8().constData(), argCount, + &AbstractDb2::evaluateScalar, userData); + + return res == SQLITE_OK; +} + +template +bool AbstractDb2::registerAggregateFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + userDataList << userData; + + int res = sqlite_create_aggregate(dbHandle, name.toUtf8().constData(), argCount, + &AbstractDb2::evaluateAggregateStep, + &AbstractDb2::evaluateAggregateFinal, + userData); + + return res == SQLITE_OK; +} + +template +bool AbstractDb2::registerCollationInternal(const QString& name) +{ + // Not supported in SQLite 2 + UNUSED(name); + return false; +} + +template +bool AbstractDb2::deregisterCollationInternal(const QString& name) +{ + // Not supported in SQLite 2 + UNUSED(name); + return false; +} + +template +void AbstractDb2::cleanUp() +{ + for (Query* q : queries) + q->finalize(); +} + +template +void AbstractDb2::resetError() +{ + dbErrorCode = 0; + dbErrorMessage = QString::null; +} + +template +void AbstractDb2::storeResult(sqlite_func* func, const QVariant& result, bool ok) +{ + if (!ok) + { + QByteArray ba = result.toString().toUtf8(); + sqlite_set_result_error(func, ba.constData(), ba.size()); + return; + } + + // Code below is a modified code from Qt (its SQLite plugin). + if (result.isNull()) + { + sqlite_set_result_string(func, nullptr, -1); + return; + } + + switch (result.type()) + { + case QVariant::ByteArray: + { + QByteArray ba = result.toByteArray(); + sqlite_set_result_string(func, ba.constData(), ba.size()); + break; + } + case QVariant::Int: + case QVariant::Bool: + case QVariant::UInt: + case QVariant::LongLong: + { + sqlite_set_result_int(func, result.toInt()); + break; + } + case QVariant::Double: + { + sqlite_set_result_double(func, result.toDouble()); + break; + } + case QVariant::List: + { + QList list = result.toList(); + QStringList strList; + for (const QVariant& v : list) + strList << v.toString(); + + QByteArray ba = strList.join(" ").toUtf8(); + sqlite_set_result_string(func, ba.constData(), ba.size()); + break; + } + case QVariant::StringList: + { + QByteArray ba = result.toStringList().join(" ").toUtf8(); + sqlite_set_result_string(func, ba.constData(), ba.size()); + break; + } + default: + { + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + QByteArray ba = result.toString().toUtf8(); + sqlite_set_result_string(func, ba.constData(), ba.size()); + break; + } + } +} + +template +QList AbstractDb2::getArgs(int argCount, const char** args) +{ + QList results; + + for (int i = 0; i < argCount; i++) + { + if (!args[i]) + { + results << QVariant(); + continue; + } + + results << QString::fromUtf8(args[i]); + } + return results; +} + +template +void AbstractDb2::evaluateScalar(sqlite_func* func, int argCount, const char** args) +{ + QList argList = getArgs(argCount, args); + bool ok = true; + QVariant result = AbstractDb::evaluateScalar(sqlite_user_data(func), argList, ok); + storeResult(func, result, ok); +} + +template +void AbstractDb2::evaluateAggregateStep(sqlite_func* func, int argCount, const char** args) +{ + void* dataPtr = sqlite_user_data(func); + QList argList = getArgs(argCount, args); + QHash aggregateContext = getAggregateContext(func); + + AbstractDb::evaluateAggregateStep(dataPtr, aggregateContext, argList); + + setAggregateContext(func, aggregateContext); +} + +template +void AbstractDb2::evaluateAggregateFinal(sqlite_func* func) +{ + void* dataPtr = sqlite_user_data(func); + QHash aggregateContext = getAggregateContext(func); + + bool ok = true; + QVariant result = AbstractDb::evaluateAggregateFinal(dataPtr, aggregateContext, ok); + + storeResult(func, result, ok); + releaseAggregateContext(func); +} + +template +void*AbstractDb2::getContextMemPtr(sqlite_func* func) +{ + return sqlite_aggregate_context(func, sizeof(QHash**)); +} + +template +QHash AbstractDb2::getAggregateContext(sqlite_func* func) +{ + return AbstractDb::getAggregateContext(getContextMemPtr(func)); +} + +template +void AbstractDb2::setAggregateContext(sqlite_func* func, const QHash& aggregateContext) +{ + AbstractDb::setAggregateContext(getContextMemPtr(func), aggregateContext); +} + +template +void AbstractDb2::releaseAggregateContext(sqlite_func* func) +{ + AbstractDb::releaseAggregateContext(getContextMemPtr(func)); +} + +//------------------------------------------------------------------------------------ +// Query +//------------------------------------------------------------------------------------ + +template +AbstractDb2::Query::Query(AbstractDb2* db, const QString& query) : + db(db) +{ + this->query = query; + db->queries << this; +} + +template +AbstractDb2::Query::~Query() +{ + if (db.isNull()) + return; + + finalize(); + db->queries.removeOne(this); +} + +template +void AbstractDb2::Query::copyErrorFromDb() +{ + if (db->dbErrorCode != 0) + { + errorCode = db->dbErrorCode; + errorMessage = db->dbErrorMessage; + return; + } +} + +template +void AbstractDb2::Query::copyErrorToDb() +{ + db->dbErrorCode = errorCode; + db->dbErrorMessage = errorMessage; +} + +template +void AbstractDb2::Query::setError(int code, const QString& msg) +{ + if (errorCode != SQLITE_OK) + return; // don't overwrite first error + + errorCode = code; + errorMessage = msg; + copyErrorToDb(); +} + +template +int AbstractDb2::Query::prepareStmt(const QString& processedQuery) +{ + char* errMsg = nullptr; + const char* tail; + QByteArray queryBytes = processedQuery.toUtf8(); + int res = sqlite_compile(db->dbHandle, queryBytes.constData(), &tail, &stmt, &errMsg); + if (res != SQLITE_OK) + { + finalize(); + if (errMsg) + { + setError(res, QString::fromUtf8((errMsg))); + sqlite_freemem(errMsg); + } + return res; + } + + if (tail && !QString::fromUtf8(tail).trimmed().isEmpty()) + qWarning() << "Executed query left with tailing contents:" << tail << ", while executing query:" << query; + + return SQLITE_OK; +} + +template +int AbstractDb2::Query::resetStmt() +{ + errorCode = 0; + errorMessage = QString::null; + affected = 0; + colCount = -1; + rowAvailable = false; + nextRowValues.clear(); + + char* errMsg = nullptr; + int res = sqlite_reset(stmt, &errMsg); + if (res != SQLITE_OK) + { + stmt = nullptr; + if (errMsg) + { + setError(res, QString::fromUtf8((errMsg))); + sqlite_freemem(errMsg); + } + return res; + } + return SQLITE_OK; +} + +template +bool AbstractDb2::Query::execInternal(const QList& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite2, flags.testFlag(Db::Flag::NO_LOCK)); + + QueryWithParamCount queryWithParams = getQueryWithParamCount(query, Dialect::Sqlite2); + QString singleStr = replaceNamedParams(queryWithParams.first); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(singleStr); + + if (res != SQLITE_OK) + return false; + + for (int paramIdx = 1; paramIdx <= queryWithParams.second; paramIdx++) + { + res = bindParam(paramIdx, args[paramIdx-1]); + if (res != SQLITE_OK) + return false; + } + + bool ok = (fetchFirst() == SQLITE_OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template +bool AbstractDb2::Query::execInternal(const QHash& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite2, flags.testFlag(Db::Flag::NO_LOCK)); + + QueryWithParamNames queryWithParams = getQueryWithParamNames(query, Dialect::Sqlite2); + QString singleStr = replaceNamedParams(queryWithParams.first); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(singleStr); + + if (res != SQLITE_OK) + return false; + + int paramIdx = 1; + foreach (const QString& paramName, queryWithParams.second) + { + if (!args.contains(paramName)) + { + setError(SqlErrorCode::OTHER_EXECUTION_ERROR, "Error while preparing statement: could not bind parameter " + paramName); + return false; + } + + res = bindParam(paramIdx++, args[paramName]); + if (res != SQLITE_OK) + return false; + } + + bool ok = (fetchFirst() == SQLITE_OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template +QString AbstractDb2::Query::replaceNamedParams(const QString& query) +{ + TokenList tokens = Lexer::tokenize(query, Dialect::Sqlite2); + for (TokenPtr token : tokens) + { + if (token->type == Token::BIND_PARAM) + token->value = "?"; + } + return tokens.detokenize(); +} + +template +int AbstractDb2::Query::bindParam(int paramIdx, const QVariant& value) +{ + if (value.isNull()) + { + return sqlite_bind(stmt, paramIdx, nullptr, 0, 0); + } + + switch (value.type()) + { + case QVariant::ByteArray: + { + // NOTE: SQLite 2 has a bug that makes it impossible to write BLOB with nulls inside. First occurrance of the null + // makes the whole value to be saved as truncated to that position. Nothing I can do about it. + QByteArray ba = value.toByteArray(); + return sqlite_bind(stmt, paramIdx, ba.constData(), ba.size(), true); + } + default: + { + QByteArray ba = value.toString().toUtf8(); + ba.append('\0'); + return sqlite_bind(stmt, paramIdx, ba.constData(), ba.size(), true); + } + } + + return SQLITE_MISUSE; // not going to happen +} +template +QString AbstractDb2::Query::getErrorText() +{ + return errorMessage; +} + +template +int AbstractDb2::Query::getErrorCode() +{ + return errorCode; +} + +template +QStringList AbstractDb2::Query::getColumnNames() +{ + return colNames; +} + +template +int AbstractDb2::Query::columnCount() +{ + return colCount; +} + +template +qint64 AbstractDb2::Query::rowsAffected() +{ + return affected; +} + +template +SqlResultsRowPtr AbstractDb2::Query::nextInternal() +{ + if (!rowAvailable || db.isNull()) + return SqlResultsRowPtr(); + + Row* row = new Row; + row->init(colNames, nextRowValues); + + int res = fetchNext(); + if (res != SQLITE_OK) + { + delete row; + return SqlResultsRowPtr(); + } + return SqlResultsRowPtr(row); +} + +template +bool AbstractDb2::Query::hasNextInternal() +{ + return rowAvailable && stmt; +} + +template +int AbstractDb2::Query::fetchFirst() +{ + rowAvailable = true; + int res = fetchNext(); + if (res == SQLITE_OK) + { + if (colCount == 0) + { + affected = 0; + } + else + { + affected = sqlite_changes(db->dbHandle); + insertRowId["ROWID"] = sqlite_last_insert_rowid(db->dbHandle); + } + } + return res; +} + +template +bool AbstractDb2::Query::checkDbState() +{ + if (db.isNull() || !db->dbHandle) + { + setError(SqlErrorCode::DB_NOT_DEFINED, "SqlQuery is no longer valid."); + return false; + } + + return true; +} + +template +QString AbstractDb2::Query::finalize() +{ + QString msg; + if (stmt) + { + char* errMsg = nullptr; + sqlite_finalize(stmt, &errMsg); + stmt = nullptr; + if (errMsg) + { + msg = QString::fromUtf8(errMsg); + sqlite_freemem(errMsg); + } + } + return msg; +} + +template +int AbstractDb2::Query::fetchNext() +{ + if (!checkDbState()) + rowAvailable = false; + + if (!rowAvailable || !stmt) + { + setError(SQLITE_MISUSE, tr("Result set expired or no row available.")); + return SQLITE_MISUSE; + } + + rowAvailable = false; + + const char** values; + const char** columns; + int columnsCount; + + int res; + int secondsSpent = 0; + while ((res = sqlite_step(stmt, &columnsCount, &values, &columns)) == SQLITE_BUSY && secondsSpent < db->getTimeout()) + { + QThread::sleep(1); + if (db->getTimeout() >= 0) + secondsSpent++; + } + + switch (res) + { + case SQLITE_ROW: + rowAvailable = true; + break; + case SQLITE_DONE: + // Empty pointer as no more results are available. + break; + default: + setError(res, finalize()); + return SQLITE_ERROR; + } + + // First row, initialize members + if (colCount == -1) + init(columnsCount, columns); + + // Then read the next row data + nextRowValues.clear(); + if (rowAvailable) + { + for (int i = 0; i < colCount; i++) + { + if (isBinaryColumn(i)) + nextRowValues << QByteArray(values[i]); + else + nextRowValues << QString::fromUtf8(values[i]); + } + } + + return SQLITE_OK; +} + +template +void AbstractDb2::Query::init(int columnsCount, const char** columns) +{ + colCount = columnsCount; + + TokenList columnDescription; + for (int i = 0; i < colCount; i++) + { + columnDescription = Lexer::tokenize(QString::fromUtf8(columns[i]), Dialect::Sqlite2).filterWhiteSpaces(); + if (columnDescription.size() > 0) + { + // If the column is prefixed with dbname and table name, then we remove them. + for (int j = 0; j < 2 &&columnDescription.size() > 1 && columnDescription[1]->type == Token::OPERATOR && columnDescription[1]->value == "."; j++) + { + columnDescription.removeFirst(); + columnDescription.removeFirst(); + } + + colNames << stripObjName(columnDescription.first()->value, Dialect::Sqlite2); + } + else + colNames << ""; + } +} + +//------------------------------------------------------------------------------------ +// Row +//------------------------------------------------------------------------------------ + +template +void AbstractDb2::Query::Row::init(const QStringList& columns, const QList& resultValues) +{ + for (int i = 0; i < columns.size(); i++) + { + values << resultValues[i]; + valuesMap[columns[i]] = resultValues[i]; + } +} + +#endif // ABSTRACTDB2_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h new file mode 100644 index 0000000..d8e54b4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h @@ -0,0 +1,1157 @@ +#ifndef ABSTRACTDB3_H +#define ABSTRACTDB3_H + +#include "db/abstractdb.h" +#include "parser/lexer.h" +#include "common/utils_sql.h" +#include "common/unused.h" +#include "services/collationmanager.h" +#include "sqlitestudio.h" +#include "db/sqlerrorcodes.h" +#include +#include +#include + +/** + * @brief Complete implementation of SQLite 3 driver for SQLiteStudio. + * + * Inherit this when implementing Db for SQLite 3. In most cases you will only need + * to create one public constructor, which forwards parameters to the AbstractDb constructor. + * This be sufficient to implement SQLite 3 database plugin. + * Just link it with proper SQLite library. + * + * The template parameter should provide all necessary SQLite symbols used by this implementation. + * This way every Db plugin can provide it's own symbols to work on SQLite and so it allows + * for loading multiple SQLite libraries into the same application, while symbols in each library + * can be different (and should be different, to avoid name conflicts and symbol overlapping). + * See how it's done in dbsqlite3.h. + * + * @see DbQt + */ +template +class AbstractDb3 : public AbstractDb +{ + public: + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See AbstractDb for details. + * + * All values from this constructor are just passed to AbstractDb constructor. + */ + AbstractDb3(const QString& name, const QString& path, const QHash& connOptions); + ~AbstractDb3(); + + protected: + bool isOpenInternal(); + void interruptExecution(); + QString getErrorTextInternal(); + int getErrorCodeInternal(); + bool openInternal(); + bool closeInternal(); + void initAfterOpen(); + SqlQueryPtr prepare(const QString& query); + QString getTypeLabel(); + bool deregisterFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount); + bool registerAggregateFunction(const QString& name, int argCount); + bool registerCollationInternal(const QString& name); + bool deregisterCollationInternal(const QString& name); + + private: + class Query : public SqlQuery + { + public: + class Row : public SqlResultsRow + { + public: + int init(const QStringList& columns, typename T::stmt* stmt); + + private: + int getValue(typename T::stmt* stmt, int col, QVariant& value); + }; + + Query(AbstractDb3* db, const QString& query); + ~Query(); + + QString getErrorText(); + int getErrorCode(); + QStringList getColumnNames(); + int columnCount(); + qint64 rowsAffected(); + void finalize(); + + protected: + SqlResultsRowPtr nextInternal(); + bool hasNextInternal(); + bool execInternal(const QList& args); + bool execInternal(const QHash& args); + + private: + int prepareStmt(); + int resetStmt(); + int bindParam(int paramIdx, const QVariant& value); + int fetchFirst(); + int fetchNext(); + bool checkDbState(); + void copyErrorFromDb(); + void copyErrorToDb(); + void setError(int code, const QString& msg); + + QPointer> db; + typename T::stmt* stmt = nullptr; + int errorCode = T::OK; + QString errorMessage; + int colCount = 0; + QStringList colNames; + int affected = 0; + bool rowAvailable = false; + }; + + struct CollationUserData + { + QString name; + AbstractDb3* db = nullptr; + }; + + QString extractLastError(); + void cleanUp(); + void resetError(); + + /** + * @brief Registers function to call when unknown collation was encountered by the SQLite. + * + * For unknown collations SQLite calls function registered by this method and expects it to register + * proper function handling that collation, otherwise the query will result with error. + * + * The default collation handler does a simple QString::compare(), case insensitive. + */ + void registerDefaultCollationRequestHandler(); + + /** + * @brief Stores given result in function's context. + * @param context Custom SQL function call context. + * @param result Value returned from function execution. + * @param ok true if the result is from a successful execution, or false if the result contains error message (QString). + * + * This method is called after custom implementation of the function was evaluated and it returned the result. + * It stores the result in function's context, so it becomes the result of the function call. + */ + static void storeResult(typename T::context* context, const QVariant& result, bool ok); + + /** + * @brief Converts SQLite arguments into the list of argument values. + * @param argCount Number of arguments. + * @param args SQLite argument values. + * @return Convenient Qt list with argument values as QVariant. + * + * This function does necessary conversions reflecting internal SQLite datatype, so if the type + * was for example BLOB, then the QVariant will be a QByteArray, etc. + */ + static QList getArgs(int argCount, typename T::value** args); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param context SQL function call context. + * @param argCount Number of arguments passed to the function. + * @param args Arguments passed to the function. + * + * This method is aware of the implementation language and the code defined for it, + * so it delegates the execution to the proper plugin handling that language. Then it stores + * result returned from the plugin in function's context, so it becomes function's result. + * + * This method is called for scalar functions. + * + * @see DbQt::evaluateScalar() + */ + static void evaluateScalar(typename T::context* context, int argCount, typename T::value** args); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param context SQL function call context. + * @param argCount Number of arguments passed to the function. + * @param args Arguments passed to the function. + * + * This method is called for aggregate functions. + * + * If this is the first call to this function using this context, then it will execute + * both "initial" and then "per step" code for this function implementation. + * + * @see DbQt3::evaluateScalar() + * @see DbQt::evaluateAggregateStep() + */ + static void evaluateAggregateStep(typename T::context* context, int argCount, typename T::value** args); + + /** + * @brief Evaluates "final" code for aggregate function. + * @param context SQL function call context. + * + * This method is called for aggregate functions. + * + * It's called once, at the end of aggregate function evaluation. + * It executes "final" code of the function implementation. + */ + static void evaluateAggregateFinal(typename T::context* context); + + /** + * @brief Evaluates code of the collation. + * @param userData Collation user data (name of the collation inside). + * @param length1 Number of characters in value1 (excluding \0). + * @param value1 First value to compare. + * @param length2 Number of characters in value2 (excluding \0). + * @param value2 Second value to compare. + * @return -1, 0, or 1, as SQLite's collation specification demands it. + */ + static int evaluateCollation(void* userData, int length1, const void* value1, int length2, const void* value2); + + /** + * @brief Cleans up collation user data when collation is deregistered. + * @param userData User data to delete. + */ + static void deleteCollationUserData(void* userData); + + /** + * @brief Destructor for function user data object. + * @param dataPtr Pointer to the user data object. + * + * This is called by SQLite when the function is deregistered. + */ + static void deleteUserData(void* dataPtr); + + /** + * @brief Allocates and/or returns shared memory for the aggregate SQL function call. + * @param context SQL function call context. + * @return Pointer to the memory. + * + * It allocates exactly the number of bytes required to store pointer to a QHash. + * The memory is released after the aggregate function is finished. + */ + static void* getContextMemPtr(typename T::context* context); + + /** + * @brief Allocates and/or returns QHash shared across all aggregate function steps. + * @param context SQL function call context. + * @return Shared hash table. + * + * The hash table is created before initial aggregate function step is made. + * Then it's shared across all further steps (using this method to get it) + * and then releases the memory after the last (final) step of the function call. + */ + static QHash getAggregateContext(typename T::context* context); + + /** + * @brief Sets new value of the aggregate function shared hash table. + * @param context SQL function call context. + * @param aggregateContext New shared hash table value to store. + * + * This should be called after each time the context was requested with getAggregateContext() and then modified. + */ + static void setAggregateContext(typename T::context* context, const QHash& aggregateContext); + + /** + * @brief Releases aggregate function shared hash table. + * @param context SQL function call context. + * + * This should be called from final aggregate function step to release the shared context (delete QHash). + * The memory used to store pointer to the shared context will be released by the SQLite itself. + */ + static void releaseAggregateContext(typename T::context* context); + + /** + * @brief Registers default collation for requested collation. + * @param fnUserData User data passed when registering collation request handling function. + * @param fnDbHandle Database handle for which this call is being made. + * @param eTextRep Text encoding (for now always T::UTF8). + * @param collationName Name of requested collation. + * + * This function is called by SQLite to order registering collation with given name. We register default collation, + * cause all known collations should already be registered. + * + * Default collation is implemented by evaluateDefaultCollation(). + */ + static void registerDefaultCollation(void* fnUserData, typename T::handle* fnDbHandle, int eTextRep, const char* collationName); + + /** + * @brief Called as a default collation implementation. + * @param userData Collation user data, not used. + * @param length1 Number of characters in value1 (excluding \0). + * @param value1 First value to compare. + * @param length2 Number of characters in value2 (excluding \0). + * @param value2 Second value to compare. + * @return -1, 0, or 1, as SQLite's collation specification demands it. + */ + static int evaluateDefaultCollation(void* userData, int length1, const void* value1, int length2, const void* value2); + + typename T::handle* dbHandle = nullptr; + QString dbErrorMessage; + int dbErrorCode = T::OK; + QList queries; + + /** + * @brief User data for default collation request handling function. + * + * That function doesn't have destructor function pointer, so we need to keep track of that user data + * and delete it when database is closed. + */ + CollationUserData* defaultCollationUserData = nullptr; +}; + +//------------------------------------------------------------------------------------ +// AbstractDb3 +//------------------------------------------------------------------------------------ + +template +AbstractDb3::AbstractDb3(const QString& name, const QString& path, const QHash& connOptions) : + AbstractDb(name, path, connOptions) +{ +} + +template +AbstractDb3::~AbstractDb3() +{ + if (isOpenInternal()) + closeInternal(); +} + +template +bool AbstractDb3::isOpenInternal() +{ + return dbHandle != nullptr; +} + +template +void AbstractDb3::interruptExecution() +{ + if (!isOpenInternal()) + return; + + T::interrupt(dbHandle); +} + +template +QString AbstractDb3::getErrorTextInternal() +{ + return dbErrorMessage; +} + +template +int AbstractDb3::getErrorCodeInternal() +{ + return dbErrorCode; +} + +template +bool AbstractDb3::openInternal() +{ + resetError(); + typename T::handle* handle = nullptr; + int res = T::open_v2(path.toUtf8().constData(), &handle, T::OPEN_READWRITE|T::OPEN_CREATE, nullptr); + if (res != T::OK) + { + if (handle) + T::close(handle); + + dbErrorMessage = tr("Could not open database: %1").arg(extractLastError()); + dbErrorCode = res; + return false; + } + dbHandle = handle; + return true; +} + +template +bool AbstractDb3::closeInternal() +{ + resetError(); + if (!dbHandle) + return false; + + cleanUp(); + + int res = T::close(dbHandle); + if (res != T::OK) + { + dbErrorMessage = tr("Could not close database: %1").arg(extractLastError()); + dbErrorCode = res; + qWarning() << "Error closing database. That's weird:" << dbErrorMessage; + return false; + } + dbHandle = nullptr; + return true; +} + +template +void AbstractDb3::initAfterOpen() +{ + T::enable_load_extension(dbHandle, true); + registerDefaultCollationRequestHandler();; + exec("PRAGMA foreign_keys = 1;", Flag::NO_LOCK); + exec("PRAGMA recursive_triggers = 1;", Flag::NO_LOCK); +} + +template +SqlQueryPtr AbstractDb3::prepare(const QString& query) +{ + return SqlQueryPtr(new Query(this, query)); +} + +template +QString AbstractDb3::getTypeLabel() +{ + return T::label; +} + +template +bool AbstractDb3::deregisterFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + T::create_function(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, 0, nullptr, nullptr, nullptr); + return true; +} + +template +bool AbstractDb3::registerScalarFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + + int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, userData, + &AbstractDb3::evaluateScalar, + nullptr, + nullptr, + &AbstractDb3::deleteUserData); + + return res == T::OK; +} + +template +bool AbstractDb3::registerAggregateFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + + int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, userData, + nullptr, + &AbstractDb3::evaluateAggregateStep, + &AbstractDb3::evaluateAggregateFinal, + &AbstractDb3::deleteUserData); + + return res == T::OK; +} + +template +bool AbstractDb3::registerCollationInternal(const QString& name) +{ + if (!dbHandle) + return false; + + CollationUserData* userData = new CollationUserData; + userData->name = name; + + int res = T::create_collation_v2(dbHandle, name.toUtf8().constData(), T::UTF8, userData, + &AbstractDb3::evaluateCollation, + &AbstractDb3::deleteCollationUserData); + return res == T::OK; +} + +template +bool AbstractDb3::deregisterCollationInternal(const QString& name) +{ + if (!dbHandle) + return false; + + T::create_collation_v2(dbHandle, name.toUtf8().constData(), T::UTF8, nullptr, nullptr, nullptr); + return true; +} + +template +QString AbstractDb3::extractLastError() +{ + dbErrorCode = T::extended_errcode(dbHandle); + dbErrorMessage = QString::fromUtf8(T::errmsg(dbHandle)); + return dbErrorMessage; +} + +template +void AbstractDb3::cleanUp() +{ + for (Query* q : queries) + q->finalize(); + + safe_delete(defaultCollationUserData); +} + +template +void AbstractDb3::resetError() +{ + dbErrorCode = 0; + dbErrorMessage = QString::null; +} + +template +void AbstractDb3::storeResult(typename T::context* context, const QVariant& result, bool ok) +{ + if (!ok) + { + QString str = result.toString(); + T::result_error16(context, str.utf16(), str.size() * sizeof(QChar)); + return; + } + + // Code below is a modified code from Qt (its SQLite plugin). + if (result.isNull()) + { + T::result_null(context); + return; + } + + switch (result.type()) + { + case QVariant::ByteArray: + { + QByteArray ba = result.toByteArray(); + T::result_blob(context, ba.constData(), ba.size(), T::TRANSIENT()); + break; + } + case QVariant::Int: + case QVariant::Bool: + { + T::result_int(context, result.toInt()); + break; + } + case QVariant::Double: + { + T::result_double(context, result.toDouble()); + break; + } + case QVariant::UInt: + case QVariant::LongLong: + { + T::result_int64(context, result.toLongLong()); + break; + } + case QVariant::List: + { + QList list = result.toList(); + QStringList strList; + for (const QVariant& v : list) + strList << v.toString(); + + QString str = strList.join(" "); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + case QVariant::StringList: + { + QString str = result.toStringList().join(" "); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + default: + { + // T::TRANSIENT makes sure that sqlite buffers the data + QString str = result.toString(); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + } +} + +template +QList AbstractDb3::getArgs(int argCount, typename T::value** args) +{ + int dataType; + QList results; + QVariant value; + + // The loop below uses slightly modified code from Qt (its SQLite plugin) to extract values. + for (int i = 0; i < argCount; i++) + { + dataType = T::value_type(args[i]); + switch (dataType) + { + case T::INTEGER: + value = T::value_int64(args[i]); + break; + case T::BLOB: + value = QByteArray( + static_cast(T::value_blob(args[i])), + T::value_bytes(args[i]) + ); + break; + case T::FLOAT: + value = T::value_double(args[i]); + break; + case T::NULL_TYPE: + value = QVariant(QVariant::String); + break; + default: + value = QString( + reinterpret_cast(T::value_text16(args[i])), + T::value_bytes16(args[i]) / sizeof(QChar) + ); + break; + } + results << value; + } + return results; +} + +template +void AbstractDb3::evaluateScalar(typename T::context* context, int argCount, typename T::value** args) +{ + QList argList = getArgs(argCount, args); + bool ok = true; + QVariant result = AbstractDb::evaluateScalar(T::user_data(context), argList, ok); + storeResult(context, result, ok); +} + +template +void AbstractDb3::evaluateAggregateStep(typename T::context* context, int argCount, typename T::value** args) +{ + void* dataPtr = T::user_data(context); + QList argList = getArgs(argCount, args); + QHash aggregateContext = getAggregateContext(context); + + AbstractDb::evaluateAggregateStep(dataPtr, aggregateContext, argList); + + setAggregateContext(context, aggregateContext); +} + +template +void AbstractDb3::evaluateAggregateFinal(typename T::context* context) +{ + void* dataPtr = T::user_data(context); + QHash aggregateContext = getAggregateContext(context); + + bool ok = true; + QVariant result = AbstractDb::evaluateAggregateFinal(dataPtr, aggregateContext, ok); + + storeResult(context, result, ok); + releaseAggregateContext(context); +} + +template +int AbstractDb3::evaluateCollation(void* userData, int length1, const void* value1, int length2, const void* value2) +{ + UNUSED(length1); + UNUSED(length2); + CollationUserData* collUserData = reinterpret_cast(userData); + return COLLATIONS->evaluate(collUserData->name, QString::fromUtf8((const char*)value1), QString::fromUtf8((const char*)value2)); +} + +template +void AbstractDb3::deleteCollationUserData(void* userData) +{ + if (!userData) + return; + + CollationUserData* collUserData = reinterpret_cast(userData); + delete collUserData; +} + +template +void AbstractDb3::deleteUserData(void* dataPtr) +{ + if (!dataPtr) + return; + + FunctionUserData* userData = reinterpret_cast(dataPtr); + delete userData; +} + +template +void* AbstractDb3::getContextMemPtr(typename T::context* context) +{ + return T::aggregate_context(context, sizeof(QHash**)); +} + +template +QHash AbstractDb3::getAggregateContext(typename T::context* context) +{ + return AbstractDb::getAggregateContext(getContextMemPtr(context)); +} + +template +void AbstractDb3::setAggregateContext(typename T::context* context, const QHash& aggregateContext) +{ + AbstractDb::setAggregateContext(getContextMemPtr(context), aggregateContext); +} + +template +void AbstractDb3::releaseAggregateContext(typename T::context* context) +{ + AbstractDb::releaseAggregateContext(getContextMemPtr(context)); +} + +template +void AbstractDb3::registerDefaultCollation(void* fnUserData, typename T::handle* fnDbHandle, int eTextRep, const char* collationName) +{ + CollationUserData* defUserData = reinterpret_cast(fnUserData); + if (!defUserData) + { + qWarning() << "Null userData in AbstractDb3::registerDefaultCollation()."; + return; + } + + AbstractDb3* db = defUserData->db; + if (!db) + { + qWarning() << "No database defined in userData of AbstractDb3::registerDefaultCollation()."; + return; + } + + // If SQLite seeks for collation implementation with different encoding, we force it to use existing one. + if (db->isCollationRegistered(QString::fromUtf8(collationName))) + return; + + // Check if dbHandle matches - just in case + if (db->dbHandle != fnDbHandle) + { + qWarning() << "Mismatch of dbHandle in AbstractDb3::registerDefaultCollation()."; + return; + } + + int res = T::create_collation_v2(fnDbHandle, collationName, eTextRep, nullptr, + &AbstractDb3::evaluateDefaultCollation, nullptr); + + if (res != T::OK) + qWarning() << "Could not register default collation in AbstractDb3::registerDefaultCollation()."; +} + +template +int AbstractDb3::evaluateDefaultCollation(void* userData, int length1, const void* value1, int length2, const void* value2) +{ + UNUSED(userData); + UNUSED(length1); + UNUSED(length2); + return COLLATIONS->evaluateDefault(QString::fromUtf8((const char*)value1), QString::fromUtf8((const char*)value2)); +} + +template +void AbstractDb3::registerDefaultCollationRequestHandler() +{ + if (!dbHandle) + return; + + defaultCollationUserData = new CollationUserData; + defaultCollationUserData->db = this; + + int res = T::collation_needed(dbHandle, defaultCollationUserData, &AbstractDb3::registerDefaultCollation); + if (res != T::OK) + qWarning() << "Could not register default collation request handler. Unknown collations will cause errors."; +} + +//------------------------------------------------------------------------------------ +// Results +//------------------------------------------------------------------------------------ + +template +AbstractDb3::Query::Query(AbstractDb3* db, const QString& query) : + db(db) +{ + this->query = query; + db->queries << this; +} + +template +AbstractDb3::Query::~Query() +{ + if (db.isNull()) + return; + + finalize(); + db->queries.removeOne(this); +} + +template +void AbstractDb3::Query::copyErrorFromDb() +{ + if (db->dbErrorCode != 0) + { + errorCode = db->dbErrorCode; + errorMessage = db->dbErrorMessage; + return; + } +} + +template +void AbstractDb3::Query::copyErrorToDb() +{ + db->dbErrorCode = errorCode; + db->dbErrorMessage = errorMessage; +} + +template +void AbstractDb3::Query::setError(int code, const QString& msg) +{ + if (errorCode != T::OK) + return; // don't overwrite first error + + errorCode = code; + errorMessage = msg; + copyErrorToDb(); +} + +template +int AbstractDb3::Query::prepareStmt() +{ + const char* tail; + QByteArray queryBytes = query.toUtf8(); + int res = T::prepare_v2(db->dbHandle, queryBytes.constData(), queryBytes.size(), &stmt, &tail); + if (res != T::OK) + { + stmt = nullptr; + db->extractLastError(); + copyErrorFromDb(); + return res; + } + + if (tail && !QString::fromUtf8(tail).trimmed().isEmpty()) + qWarning() << "Executed query left with tailing contents:" << tail << ", while executing query:" << query; + + return T::OK; +} + +template +int AbstractDb3::Query::resetStmt() +{ + errorCode = 0; + errorMessage = QString::null; + affected = 0; + colCount = -1; + rowAvailable = false; + + int res = T::reset(stmt); + if (res != T::OK) + { + stmt = nullptr; + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return res; + } + return T::OK; +} + +template +bool AbstractDb3::Query::execInternal(const QList& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite3, flags.testFlag(Db::Flag::NO_LOCK)); + QueryWithParamCount queryWithParams = getQueryWithParamCount(query, Dialect::Sqlite3); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(); + + if (res != T::OK) + return false; + + for (int paramIdx = 1; paramIdx <= queryWithParams.second; paramIdx++) + { + res = bindParam(paramIdx, args[paramIdx-1]); + if (res != T::OK) + { + db->extractLastError(); + copyErrorFromDb(); + return false; + } + } + + bool ok = (fetchFirst() == T::OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template +bool AbstractDb3::Query::execInternal(const QHash& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite3, flags.testFlag(Db::Flag::NO_LOCK)); + + QueryWithParamNames queryWithParams = getQueryWithParamNames(query, Dialect::Sqlite3); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(); + + if (res != T::OK) + return false; + + int paramIdx = 1; + foreach (const QString& paramName, queryWithParams.second) + { + if (!args.contains(paramName)) + { + setError(SqlErrorCode::OTHER_EXECUTION_ERROR, "Error while preparing statement: could not bind parameter " + paramName); + return false; + } + + res = bindParam(paramIdx++, args[paramName]); + if (res != T::OK) + { + db->extractLastError(); + copyErrorFromDb(); + return false; + } + } + + bool ok = (fetchFirst() == T::OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template +int AbstractDb3::Query::bindParam(int paramIdx, const QVariant& value) +{ + if (value.isNull()) + { + return T::bind_null(stmt, paramIdx); + } + + switch (value.type()) + { + case QVariant::ByteArray: + { + QByteArray ba = value.toByteArray(); + return T::bind_blob(stmt, paramIdx, ba.constData(), ba.size(), T::TRANSIENT()); + } + case QVariant::Int: + case QVariant::Bool: + { + return T::bind_int(stmt, paramIdx, value.toInt()); + } + case QVariant::Double: + { + return T::bind_double(stmt, paramIdx, value.toDouble()); + } + case QVariant::UInt: + case QVariant::LongLong: + { + return T::bind_int64(stmt, paramIdx, value.toLongLong()); + } + default: + { + // T::TRANSIENT makes sure that sqlite buffers the data + QString str = value.toString(); + return T::bind_text16(stmt, paramIdx, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + } + } + + return T::MISUSE; // not going to happen +} + +template +bool AbstractDb3::Query::checkDbState() +{ + if (db.isNull() || !db->dbHandle) + { + setError(SqlErrorCode::DB_NOT_DEFINED, "SqlQuery is no longer valid."); + return false; + } + + return true; +} + +template +void AbstractDb3::Query::finalize() +{ + if (stmt) + { + T::finalize(stmt); + stmt = nullptr; + } +} + +template +QString AbstractDb3::Query::getErrorText() +{ + return errorMessage; +} + +template +int AbstractDb3::Query::getErrorCode() +{ + return errorCode; +} + +template +QStringList AbstractDb3::Query::getColumnNames() +{ + return colNames; +} + +template +int AbstractDb3::Query::columnCount() +{ + return colCount; +} + +template +qint64 AbstractDb3::Query::rowsAffected() +{ + return affected; +} + +template +SqlResultsRowPtr AbstractDb3::Query::nextInternal() +{ + Row* row = new Row; + int res = row->init(colNames, stmt); + if (res != T::OK) + { + delete row; + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return SqlResultsRowPtr(); + } + + res = fetchNext(); + if (res != T::OK) + { + delete row; + return SqlResultsRowPtr(); + } + + return SqlResultsRowPtr(row); +} + +template +bool AbstractDb3::Query::hasNextInternal() +{ + return rowAvailable && stmt && checkDbState(); +} + +template +int AbstractDb3::Query::fetchFirst() +{ + colCount = T::column_count(stmt); + for (int i = 0; i < colCount; i++) + colNames << QString::fromUtf8(T::column_name(stmt, i)); + + rowAvailable = true; + int res = fetchNext(); + + affected = 0; + if (res == T::OK) + { + affected = T::changes(db->dbHandle); + insertRowId["ROWID"] = T::last_insert_rowid(db->dbHandle); + } + + return res; +} + +template +int AbstractDb3::Query::fetchNext() +{ + if (!checkDbState()) + rowAvailable = false; + + if (!rowAvailable || !stmt) + { + setError(T::MISUSE, tr("Result set expired or no row available.")); + return T::MISUSE; + } + + rowAvailable = false; + int res; + int secondsSpent = 0; + while ((res = T::step(stmt)) == T::BUSY && secondsSpent < db->getTimeout()) + { + QThread::sleep(1); + if (db->getTimeout() >= 0) + secondsSpent++; + } + + switch (res) + { + case T::ROW: + rowAvailable = true; + break; + case T::DONE: + // Empty pointer as no more results are available. + break; + default: + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return T::ERROR; + } + return T::OK; +} + +//------------------------------------------------------------------------------------ +// Row +//------------------------------------------------------------------------------------ + +template +int AbstractDb3::Query::Row::init(const QStringList& columns, typename T::stmt* stmt) +{ + int res = T::OK; + QVariant value; + for (int i = 0; i < columns.size(); i++) + { + res = getValue(stmt, i, value); + if (res != T::OK) + return res; + + values << value; + valuesMap[columns[i]] = value; + } + return res; +} + +template +int AbstractDb3::Query::Row::getValue(typename T::stmt* stmt, int col, QVariant& value) +{ + int dataType = T::column_type(stmt, col); + switch (dataType) + { + case T::INTEGER: + value = T::column_int64(stmt, col); + break; + case T::BLOB: + value = QByteArray( + static_cast(T::column_blob(stmt, col)), + T::column_bytes(stmt, col) + ); + break; + case T::FLOAT: + value = T::column_double(stmt, col); + break; + case T::NULL_TYPE: + value = QVariant(QVariant::String); + break; + default: + value = QString( + reinterpret_cast(T::column_text16(stmt, col)), + T::column_bytes16(stmt, col) / sizeof(QChar) + ); + break; + } + return T::OK; +} + +#endif // ABSTRACTDB3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.cpp b/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.cpp new file mode 100644 index 0000000..ff7e51a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.cpp @@ -0,0 +1,62 @@ +#include "db/asyncqueryrunner.h" +#include "db/sqlquery.h" +#include "db/db.h" +#include + +AsyncQueryRunner::AsyncQueryRunner(const QString &query, const QVariant& args, Db::Flags flags) + : query(query), args(args), flags(flags) +{ + init(); +} + +void AsyncQueryRunner::init() +{ + setAutoDelete(false); +} + +void AsyncQueryRunner::run() +{ + if (!db || !db->isValid()) + { + qCritical() << "No Db or invalid Db defined in AsyncQueryRunner!"; + emit finished(this); + } + + SqlQueryPtr res; + if (args.userType() == QVariant::List) + { + res = db->exec(query, args.toList(), flags); + } + else if (args.userType() == QVariant::Hash) + { + res = db->exec(query, args.toHash(), flags); + } + else + { + qCritical() << "Invalid argument type in AsyncQueryRunner::run():" << args.userType(); + } + + results = SqlQueryPtr(res); + emit finished(this); +} + +SqlQueryPtr AsyncQueryRunner::getResults() +{ + return results; +} + + +void AsyncQueryRunner::setDb(Db *db) +{ + this->db = db; +} + +void AsyncQueryRunner::setAsyncId(quint32 id) +{ + asyncId = id; +} + +quint32 AsyncQueryRunner::getAsyncId() +{ + return asyncId; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.h b/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.h new file mode 100644 index 0000000..5f0e41c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.h @@ -0,0 +1,126 @@ +#ifndef ASYNCQUERYRUNNER_H +#define ASYNCQUERYRUNNER_H + +#include "db.h" + +#include +#include +#include +#include +#include +#include + +/** + * @brief Direct query executor to be run in a thread. + * + * It's an implementation of QRunnable (so it can be run simply within QThread), + * that takes query string and arguments for the query and executes the query + * in separate thread (the one that is owning the runner). + * + * The runner is not deleted automatically. Instead the slot for finished() signal + * has to delete it. It's done like that because the slot will also be interested + * in execution results and asyncId, before the runner gets deleted. + * + * What it does is simply execute Db::exec() from another thread. + * + * It's a kind of helper class that is used to implement Db::asyncExec(). + */ +class AsyncQueryRunner : public QObject, public QRunnable +{ + Q_OBJECT + + public: + /** + * @brief Creates runner and defines basic parameters. + * @param query Query string to be executed. + * @param args Parameters to the query (can be either QHash or QList). + * @param flags Execution flags, that will be later passed to Db::exec(). + * + * It's not enough to just create runner. You also need to define db with setDb() + * and asyncId with setAsyncId(). + */ + AsyncQueryRunner(const QString& query, const QVariant& args, Db::Flags flags); + + /** + * @brief Executes query. + * + * This is the major method inherited from QRunnable. It's called from another thread + * and it executes the query. + */ + void run(); + + /** + * @brief Provides result from execution. + * @return Execution results. + */ + SqlQueryPtr getResults(); + + /** + * @brief Defines database for execution. + * @param db Database object. + */ + void setDb(Db* db); + + /** + * @brief Defines asynchronous ID for this execution. + * @param id Unique ID. + */ + void setAsyncId(quint32 id); + + /** + * @brief Provides previously defined asynchronous ID. + * @return Unique asynchronous ID. + */ + quint32 getAsyncId(); + + private: + /** + * @brief Initializes default values. + */ + void init(); + + /** + * @brief Database to execute the query on. + */ + Db* db = nullptr; + + /** + * @brief Query to execute. + */ + QString query; + + /** + * @brief Results from execution. + */ + SqlQueryPtr results; + + /** + * @brief Parameters for execution. + * + * It's either QList or QHash. If it's anything else, + * then no execution will be performed and critical error will be logged. + */ + QVariant args; + + /** + * @brief The unique asynchronous ID for this query execution. + */ + quint32 asyncId; + + /** + * @brief Execution flags passed to Db::exec(). + */ + Db::Flags flags; + + signals: + /** + * @brief Emitted after the runner has finished its job. + * + * Slot connected to this signal should at least delete the runner, + * but it can also extract execution results. + */ + void finished(AsyncQueryRunner*); +}; + + +#endif // ASYNCQUERYRUNNER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp b/SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp new file mode 100644 index 0000000..bcd4890 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp @@ -0,0 +1,20 @@ +#include "attachguard.h" +#include "db/db.h" + +GuardedAttach::GuardedAttach(Db* db, Db* attachedDb, const QString& attachName) : + db(db), attachedDb(attachedDb), name(attachName) +{ +} + +GuardedAttach::~GuardedAttach() +{ + if (name.isNull()) + return; + + db->detach(attachedDb); +} + +QString GuardedAttach::getName() const +{ + return name; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/attachguard.h b/SQLiteStudio3/coreSQLiteStudio/db/attachguard.h new file mode 100644 index 0000000..be708b8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/attachguard.h @@ -0,0 +1,24 @@ +#ifndef ATTACHGUARD_H +#define ATTACHGUARD_H + +#include + +class Db; + +class GuardedAttach +{ + public: + GuardedAttach(Db* db, Db* attachedDb, const QString& attachName); + virtual ~GuardedAttach(); + + QString getName() const; + + private: + Db* db = nullptr; + Db* attachedDb = nullptr; + QString name; +}; + +typedef QSharedPointer AttachGuard; + +#endif // ATTACHGUARD_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.cpp b/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.cpp new file mode 100644 index 0000000..f36a3bd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.cpp @@ -0,0 +1,200 @@ +#include "chainexecutor.h" +#include "sqlerrorcodes.h" +#include "db/sqlquery.h" +#include + +ChainExecutor::ChainExecutor(QObject *parent) : + QObject(parent) +{ +} + +bool ChainExecutor::getTransaction() const +{ + return transaction; +} + +void ChainExecutor::setTransaction(bool value) +{ + transaction = value; +} +QStringList ChainExecutor::getQueries() const +{ + return sqls; +} + +void ChainExecutor::setQueries(const QStringList& value) +{ + sqls = value; + queryParams.clear(); +} + +void ChainExecutor::exec() +{ + if (!db) + { + emit failure(SqlErrorCode::DB_NOT_DEFINED, tr("The database for executing queries was not defined.", "chain executor")); + return; + } + + if (!db->isOpen()) + { + emit failure(SqlErrorCode::DB_NOT_OPEN, tr("The database for executing queries was not open.", "chain executor")); + return; + } + + if (transaction && !db->begin()) + { + emit failure(db->getErrorCode(), tr("Could not start a database transaction. Details: %1", "chain executor").arg(db->getErrorText())); + return; + } + + currentSqlIndex = 0; + if (async) + executeCurrentSql(); + else + executeSync(); +} + +void ChainExecutor::interrupt() +{ + interrupted = true; + db->interrupt(); +} + +void ChainExecutor::executeCurrentSql() +{ + if (currentSqlIndex >= sqls.size()) + { + executionSuccessful(); + return; + } + + if (interrupted) + { + executionFailure(SqlErrorCode::INTERRUPTED, tr("Interrupted", "chain executor")); + return; + } + + asyncId = db->asyncExec(sqls[currentSqlIndex], queryParams); +} + +QList ChainExecutor::getMandatoryQueries() const +{ + return mandatoryQueries; +} + +void ChainExecutor::setMandatoryQueries(const QList& value) +{ + mandatoryQueries = value; +} + +Db* ChainExecutor::getDb() const +{ + return db; +} + +void ChainExecutor::setDb(Db* value) +{ + if (db) + disconnect(db, SIGNAL(asyncExecFinished(quint32,SqlQueryPtr)), this, SLOT(handleAsyncResults(quint32,SqlQueryPtr))); + + db = value; + + if (db) + connect(db, SIGNAL(asyncExecFinished(quint32,SqlQueryPtr)), this, SLOT(handleAsyncResults(quint32,SqlQueryPtr))); +} + + +void ChainExecutor::handleAsyncResults(quint32 asyncId, SqlQueryPtr results) +{ + if (asyncId != this->asyncId) + return; + + if (!handleResults(results)) + return; + + currentSqlIndex++; + executeCurrentSql(); +} + +void ChainExecutor::executionFailure(int errorCode, const QString& errorText) +{ + if (transaction) + db->rollback(); + + successfulExecution = false; + executionErrors << ExecutionError(errorCode, errorText); + emit failure(errorCode, errorText); +} + +void ChainExecutor::executionSuccessful() +{ + if (transaction && !db->commit()) + { + executionFailure(db->getErrorCode(), tr("Could not commit a database transaction. Details: %1", "chain executor").arg(db->getErrorText())); + return; + } + + successfulExecution = true; + emit success(); +} + +void ChainExecutor::executeSync() +{ + SqlQueryPtr results; + foreach (const QString& sql, sqls) + { + results = db->exec(sql, queryParams); + if (!handleResults(results)) + return; + + currentSqlIndex++; + } + executionSuccessful(); +} + +bool ChainExecutor::handleResults(SqlQueryPtr results) +{ + if (results->isError()) + { + if (interrupted || currentSqlIndex >= mandatoryQueries.size() || mandatoryQueries[currentSqlIndex]) + { + executionFailure(results->getErrorCode(), results->getErrorText()); + return false; + } + } + return true; +} +bool ChainExecutor::getSuccessfulExecution() const +{ + return successfulExecution; +} + +void ChainExecutor::setParam(const QString ¶mName, const QVariant &value) +{ + queryParams[paramName] = value; +} + +bool ChainExecutor::getAsync() const +{ + return async; +} + +void ChainExecutor::setAsync(bool value) +{ + async = value; +} + +QStringList ChainExecutor::getErrorsMessages() const +{ + QStringList msgs; + for (const ExecutionError& e : executionErrors) + msgs << e.second; + + return msgs; +} + +const QList& ChainExecutor::getErrors() const +{ + return executionErrors; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h b/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h new file mode 100644 index 0000000..2d3e3d3 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h @@ -0,0 +1,359 @@ +#ifndef CHAINEXECUTOR_H +#define CHAINEXECUTOR_H + +#include "db/db.h" +#include + +// TODO add parameters support for ChainExecutor. +// it requires clever api, cause there can be multiple queries and each can use differend parameters, +// while we cannot apply same parameter set for each query, cause they are bind by an available list/hash. + +/** + * @brief Simple query executor, which executes queries one by one. + * + * A query executor which lets you execute query (or many queries) + * using asynchronous execution and it gets back to the called only when + * all queries succeeded, or it failed at some query. + * + * This is very useful if there is a sequence of queries to be executed + * and you're interested only in the result of the last query. + * + * It also lets to configure if queries should be executed within transaction, + * or not. + */ +class API_EXPORT ChainExecutor : public QObject +{ + Q_OBJECT + + public: + typedef QPair ExecutionError; + + /** + * @brief Creates executor. + * @param parent Parent object for QObject. + */ + explicit ChainExecutor(QObject *parent = 0); + + /** + * @brief Tells if transactional execution is enabled. + * @return Transactional execution status. + */ + bool getTransaction() const; + + /** + * @brief Enabled or disables transactional execution. + * @param value True to enable, false to disable. + * + * Transactional execution is enabled by default. It means that all defined SQL queries + * will be executed in single SQL transaction. + */ + void setTransaction(bool value); + + /** + * @brief Provides list of SQL queries configured for this executor. + * @return List of queries. + */ + QStringList getQueries() const; + + /** + * @brief Defines list of queries to be executed. + * @param value List of query strings. + * + * This is the main mathod you're interested in when using ChainExecutor. + * This is how you define what SQL queries will be executed. + * + * Calling this method will clear any parameters defined previously with setParam(). + */ + void setQueries(const QStringList& value); + + /** + * @brief Provides currently configured database. + * @return Database that the queries are executed on in this executor. + */ + Db* getDb() const; + + /** + * @brief Defines database for executing queries. + * @param value The database object. + * + * It is necessary to define the database before executing queries, + * otherwise the start() will emit failure() signal and do nothing else. + */ + void setDb(Db* value); + + /** + * @brief Provides list of configured query mandatory flags. + * @return List of flags. + * + * See setMandatoryQueries() for details on mandatory flags. + */ + QList getMandatoryQueries() const; + + /** + * @brief Defines list of mandatory flags for queries. + * @param value List of flags - a boolean per each defined query. + * + * Setting mandatory flags lets you define which queries (defined with setSqls()) + * are mandatory for the successful execution and which are not. + * Queries are mandatory by default (when flags are not defined), + * which means that every defined query execution must be successfull, + * otherwise executor breaks the execution chain and reports error. + * + * By defining mandatory flags to false for some queries, you're telling + * to ChainExecutor, that it's okay if those queries fail and it should + * move along. + * + * For example: + * @code + * ChainExecutor executor; + * executor.setSqls({ + * "DELETE FROM table1 WHERE value = 5", + * "DELETE FROM possibly_not_existing_table WHERE column > 3", + * "INSERT INTO table1 VALUES (4, 6)" + * }); + * executor.setMandatoryQueries({true, false, true}); + * @endcode + * We defined second query to be optional, therefore if the table + * "possibly_not_existing_table" doesn't exist, that's fine. + * It will be ignored and the third query will be executed. + * If flags were not defined, then execution of second query would fail, + * executor would stop there, report error (with failure() signal) + * and the third query would not be executed. + * + * It also affects transactions. If executor was defined to execute + * in a transaction (with setTransaction()), then failed query + * that was not mandatory will also not rollback the transaction. + * + * In other words, queries marked as not mandatory are silently ignored + * when failed. + */ + void setMandatoryQueries(const QList& value); + + /** + * @brief Provides list of execution error messages. + * @return List of messages. + * + * Execution error messages usually have zero or one message, + * but if you defined some queries to be not mandatory, + * then each failed optional query will be silently ignored, + * but its error message will be stored and returned by this method. + * In that case, the result of this method can provide more than + * one message. + */ + QStringList getErrorsMessages() const; + + /** + * @brief Provides list of execution errors. + * @return List of errors. + * + * These are the same errors as returned by getErrorsMessages(), except this list contains + * both error code (as returned from SQLite) and error message. + */ + const QList& getErrors() const; + + /** + * @brief Tells if the executor is configured for asynchronous execution. + * @return Asynchronous flag value. + */ + bool getAsync() const; + + /** + * @brief Defines asynchronous execution mode. + * @param value true to enable asynchronous execution, false to disable it. + * + * Asynchronous execution causes start() to return immediately. + * + * When asynchronous mode is enabled, results of execution + * have to be handled by connecting to failed() and success() signals. + * + * If the asynchronous mode is disabled, result can be queried + * by getSuccessfulExecution() call. + */ + void setAsync(bool value); + + /** + * @brief Tells if the most recent execution was successful. + * @return true if execution was successful, or false if it failed. + * + * Successful execution means that all mandatory queries + * (see setMandatoryQueries()) executed successfully. + * Optional (not mandatory) queries do not affect result of this method. + * + * If this method returns true, it also means that success() signal + * was emitted. + * If this method returns false, it also means that failure() signal + * was emitted. + */ + bool getSuccessfulExecution() const; + + /** + * @brief Defines named parameter to bind in queries. + * @param paramName Parameter name (must include the preceding ':'). + * @param value Value for the parameter. + * + * Any parameter defined with this method will be applied to each query + * executed by the executor. If some query doesn't include parameter + * placeholder with defined name, then the parameter will simply + * not be applied to that query. + */ + void setParam(const QString& paramName, const QVariant& value); + + private: + /** + * @brief Executes query defines as the current one. + * + * Checks is there is a current query defined (pointed by currentSqlIndex). + * If there is, then executes it. If not, goes to executionSuccessful(). + * + * This is called for each next query in asynchronous mode. + */ + void executeCurrentSql(); + + /** + * @brief Handles failed execution. + * @param errorCode Error code. + * @param errorText Error message. + * + * Rolls back transaction (in case of transactional execution) and emits failure(). + */ + void executionFailure(int errorCode, const QString& errorText); + + /** + * @brief Handles successful execution. + * + * Commits transaction (in case of transactional execution) and emits success(). + */ + void executionSuccessful(); + + /** + * @brief Executes all queries synchronously. + * + * If the asynchronous mode is disabled, then this method executes all queries. + */ + void executeSync(); + + /** + * @brief Handles single query execution results. + * @param results Results from the query. + * @return true if the execution was successful, or false otherwise. + * + * If there was an error while execution, then executionFailure() is also called. + */ + bool handleResults(SqlQueryPtr results); + + /** + * @brief Database for execution. + */ + Db* db = nullptr; + + /** + * @brief Transactional execution mode. + */ + bool transaction = true; + + /** + * @brief Asynchronous execution mode. + */ + bool async = true; + + /** + * @brief Queries to be executed. + */ + QStringList sqls; + + /** + * @brief List of flags for mandatory queries. + * + * See setMandatoryQueries() for details. + */ + QList mandatoryQueries; + + /** + * @brief Index pointing to the current query in sqls list. + * + * When executing query in asynchronous mode, this index points to the next + * query that should be executed. + */ + int currentSqlIndex = -1; + + /** + * @brief Asynchronous ID of current query execution. + * + * The ID is provided by Db::asyncExec(). + */ + quint32 asyncId = -1; + + /** + * @brief Execution interrupted flag. + * + * Once the interrup() was called, this flag is set to true, + * so the executor knows that it should not execute any further queries. + */ + bool interrupted = false; + + /** + * @brief Errors raised during queries execution. + * + * In case of major failure, the error message is appended to this list, + * but when mandatory flags allow some failures, than this list may + * contain more error messages. + */ + QList executionErrors; + + /** + * @brief Successful execution indicator. + * + * This is set after execution is finished. + */ + bool successfulExecution = false; + + /** + * @brief Parameters to bind to queries. + * + * This is filled with setParam() calls and used later to bind + * parameters to executed queries. + */ + QHash queryParams; + + public slots: + /** + * @brief Interrupts query execution. + */ + void interrupt(); + + /** + * @brief Starts execution of all defined queries, one by one. + */ + void exec(); + + private slots: + /** + * @brief Handles asynchronous execution results from Db::asyncExec(). + * @param asyncId Asynchronous ID of the execution for the results. + * @param results Results returned from execution. + * + * Checks if given asynchronous ID matches the internally stored asyncId + * and if yes, then handles results and executes next query in the queue. + */ + void handleAsyncResults(quint32 asyncId, SqlQueryPtr results); + + signals: + /** + * @brief Emitted when all mandatory queries were successfully executed. + * + * See setMandatoryQueries() for details on mandatory queries. + */ + void success(); + + /** + * @brief Emitted when major error occurred while executing a query. + * @param errorCode Error code. + * @param errorText Error message. + * + * It's emitted only when mandatory query has failed execution. + * See setMandatoryQueries() for details on mandatory queries. + */ + void failure(int errorCode, const QString& errorText); +}; + +#endif // CHAINEXECUTOR_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.cpp b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp new file mode 100644 index 0000000..c89349c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp @@ -0,0 +1,57 @@ +#include "db.h" +#include + +Db::Db() +{ +} + +Db::~Db() +{ +} + +void Db::metaInit() +{ + qRegisterMetaType("Db*"); + qRegisterMetaTypeStreamOperators("Db*"); +} + +QString Db::flagsToString(Db::Flags flags) +{ + int idx = staticMetaObject.indexOfEnumerator("Flag"); + if (idx == -1) + return QString::null; + + QMetaEnum en = staticMetaObject.enumerator(idx); + return en.valueToKeys(static_cast(flags)); +} + +QDataStream &operator <<(QDataStream &out, const Db* myObj) +{ + out << reinterpret_cast(myObj); + return out; +} + + +QDataStream &operator >>(QDataStream &in, Db*& myObj) +{ + quint64 ptr; + in >> ptr; + myObj = reinterpret_cast(ptr); + return in; +} + + +void Sqlite2ColumnDataTypeHelper::setBinaryType(int columnIndex) +{ + binaryColumns << columnIndex; +} + +bool Sqlite2ColumnDataTypeHelper::isBinaryColumn(int columnIndex) const +{ + return binaryColumns.contains(columnIndex); +} + +void Sqlite2ColumnDataTypeHelper::clearBinaryTypes() +{ + binaryColumns.clear(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.h b/SQLiteStudio3/coreSQLiteStudio/db/db.h new file mode 100644 index 0000000..7d10a05 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.h @@ -0,0 +1,831 @@ +#ifndef DB_H +#define DB_H + +#include "returncode.h" +#include "dialect.h" +#include "services/functionmanager.h" +#include "common/readwritelocker.h" +#include "coreSQLiteStudio_global.h" +#include "db/attachguard.h" +#include "interruptable.h" +#include "dbobjecttype.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file */ + +class AsyncQueryRunner; +class Db; +class DbManager; +class SqlQuery; + +typedef QSharedPointer SqlQueryPtr; + +/** + * @brief Option to make new Db instance not install any functions or collations in the database. + * + * This connection option should be used (with boolean value = true) when creating Db instance + * to be used internally (not exposed to the user) and you don't want any special features + * (like custom SQL functions, custom collations) to be registered in that database. + */ +static_char* DB_PURE_INIT = "sqlitestudio_pure_db_initalization"; + +/** + * @brief Option name for plugin handling the database. + * + * This is a constant naming the connection option, which tells SQLiteStudio which plugin was dedicated to handle + * particular database. + */ +static_char* DB_PLUGIN = "plugin"; + +/** + * @brief Database managed by application. + * + * Everything you might want to do with SQLite databases goes through this interface in the application. + * It's has a common interface for common database operations, such as connecting and disconnecting, + * checking current status, executing queries and reading results. + * It keeps information about the database version, dialect (SQLite 2 vs SQLite 3), encoding (UTF-8, UTF-16, etc.), + * symbolic name of the database and path to the file. + * + * Regular routine with the database object would be to open it (if not open yet), execute some query + * and collect results. It can be done in several ways, but here's simple one: + * @code + * QList queryDb(const QString& dbName, const QString& colValue1, int colValue2) + * { + * // Getting database object by its name and opening it if necessary + * Db* db = DBLIST->getDb(dbName); + * if (!db) + * return; // no such database + * + * if (!db->isOpen()) + * db->open(); + * + * // Executing query and getting results + * SqlQueryPtr results = db->exec("SELECT intCol FROM table WHERE col1 = ?, col2 = ?", colValue1, colValue2) + * + * QList resultList; + * SqlResultsRowPtr row; + * while (row = results->next()) + * { + * resultList << row->value("intCol").toInt(); + * } + * return resultList; + * } + * @endcode + * + * The example above is very generic way to do things. You can use many methods which simplifies tasks in case + * you work with smaller data sets. For example: + * @code + * int getRowId(Db* db, int colVal) + * { + * // We assume that db is already open + * return db->exec("SELECT rowid FROM table WHERE column = ?", colVal).getSingleCell().toInt(); + * } + * @endcode + * + * To write some data into database you can write as this: + * @code + * void insert(Db* db, const QString& val1, int val2) + * { + * // We assume that db is already open + * db->exec("INSERT INTO table (col1, col2) VALUES (?, ?)", val1, val2); + * } + * @endcode + * + * You can use named parameters: + * @code + * void insert(Db* db, const QString& val1, int val2) + * { + * QHash params; + * params["c1"] = val1; + * params["c2"] = val2; + * db->exec("INSERT INTO table (col1, col2) VALUES (:c1, :c2)", params); + * } + * @endcode + * + * To check if the execution was successful, test results: + * @code + * void insert(Db* db, const QString& val1, int val2) + * { + * SqlQueryPtr results = db->exec("INSERT INTO table (col1, col2) VALUES (?, ?)", val1, val2); + * if (results->isError()) + * { + * qWarning() << "Error while inserting:" << results->getErrorCode() << results->getErrorText(); + * } + * } + * @endcode + * + * @see DbBase + * @see DbQt + * @see DbQt2 + * @see DbQt3 + */ +class API_EXPORT Db : public QObject, public Interruptable +{ + Q_OBJECT + + public: + /** + * @brief Flags for query execution. + * + * Those flags are used by exec() and asyncExec(). They can be used with bit-wise operators. + */ + enum class Flag + { + NONE = 0x0, /**< No flags. This is default. */ + PRELOAD = 0x1, /**< Preloads all execution results into the results object. Useful for asynchronous execution. */ + NO_LOCK = 0x2 /**< + * Prevents SQLiteStudio from setting the lock for execution on this base (not the SQLite lock, + * just a Db internal lock for multi-threading access to the Db::exec()). This should be used + * only in justified circumstances. That is when the Db call has to be done from within the part + * of code, where the lock on Db was already set. Never (!) use this to ommit lock from different + * threads. Justified situation is when you implement Db::initialDbSetup() in the derived class, + * or when you implement SqlFunctionPlugin. Don't use it for the usual cases. + */ + }; + Q_DECLARE_FLAGS(Flags, Flag) + + /** + * @brief Function to handle SQL query results. + * + * The function has to accept single results object and return nothing. + * After results are processed, they will be deleted automatically, no need to handle that. + */ + typedef std::function QueryResultsHandler; + + /** + * @brief Default, empty constructor. + */ + Db(); + + /** + * @brief Releases resources. + * + * Detaches any attached databases and closes the database if open. + */ + virtual ~Db(); + + /** + * @brief Registers Db in Qt meta subsystem. + * + * It's called at the application startup. Makes Db* supported by Qt meta subsystem. + */ + static void metaInit(); + + /** + * @brief Converts flags into string representation. + * @param flags Flags to convert. Can be multiple flags OR'ed. + * @return Flags as string representation, for example: STRING_REPLACE_ARGS. + */ + static QString flagsToString(Flags flags); + + /** + * @brief Checks if database is open (connected). + * @return true if the database is connected, or false otherwise. + */ + virtual bool isOpen() = 0; + + /** + * @brief Gets database symbolic name. + * @return Database symbolic name (as it was defined in call to DbManager#addDb() or DbManager#updateDb()). + */ + virtual QString getName() = 0; + + /** + * @brief Gets database file path. + * @return Database file path (as it was defined in call to DbManager#addDb() or DbManager#updateDb()). + */ + virtual QString getPath() = 0; + + /** + * @brief Gets SQLite version major number for this database. + * @return Major version number, that is 3 for SQLite 3.x.x and 2 for SQLite 2.x.x. + * + * You don't have to open the database. This information is always available. + */ + virtual quint8 getVersion() = 0; + + /** + * @brief Gets database dialect. + * @return Database dialect, which is either Sqlite2 or Sqlite3. + * + * You don't have to open the database. This information is always available. + */ + virtual Dialect getDialect() = 0; + + /** + * @brief Gets database encoding. + * @return Database encoding as returned from SQLite query: PRAGMA encoding; + * + * If the database is not open, then this methods quickly opens it, queries the encoding and closes the database. + * The opening and closing of the database is not visible outside, it's just an internal operation. + */ + virtual QString getEncoding() = 0; + + /** + * @brief Gets connection options. + * @return Connection options, the same as were passed to DbManager#addDb() or DbManager#updateDb(). + */ + virtual QHash& getConnectionOptions() = 0; + + /** + * @brief Sets new name for the database. + * @param value New name. + * + * This method works only on closed databases. If the database is open, then warning is logged + * and function does nothing more. + */ + virtual void setName(const QString& value) = 0; + + /** + * @brief Sets new file path for the database. + * @param value New file path. + * + * This method works only on closed databases. If the database is open, then warning is logged + * and function does nothing more. + */ + virtual void setPath(const QString& value) = 0; + + /** + * @brief Sets connection options for the database. + * @param value Connection options. See DbManager::addDb() for details. + * + * This method works only on closed databases. If the database is open, then warning is logged + * and function does nothing more. + */ + virtual void setConnectionOptions(const QHash& value) = 0; + + /** + * @brief Sets the timeout for waiting for the database to be unlocked. + * @param secs Number of seconds. + * + * When the database is locked by another application, then the SQLiteStudio will wait given number + * of seconds for the database to be released, before the execution error is reported. + * + * Set it to negative value to set infinite timeout. + * + * This doesn't involve locking done by SQLiteStudio internally (see Db::Flag::NO_LOCK), which doesn't time out. + */ + virtual void setTimeout(int secs) = 0; + + /** + * @brief Gets the current database lock waiting timeout value. + * @return Number of seconds to wait for the database to be released. + * + * See setTimeout() for details. + */ + virtual int getTimeout() const = 0; + + /** + * @brief Executes SQL query. + * @param query Query to be executed. Parameter placeholders can be either of: ?, :param, \@param, just don't mix different types in single query. + * @param args List of values to bind to parameter placeholders. As those are unnamed parameters, the order is important. + * @param flags Execution flags. + * @return Execution results. + * + * Executes SQL query and returns results. If there was an error, the results will tell you when you call SqlResults::isError(). + * + * Queries like SELECT, INSERT, UPDATE, and DELETE accept positional parameters, but only for column values. If you would like to pass table name + * for SELECT, you would have to use Flags::STRING_REPLACE_ARGS and parameter placeholders in format %1, %2, %3, and so on. You cannot mix + * string parameters (as for Flags::STRING_REPLACE_ARGS) and regular SQLite parameters in single query. If you really need to, then you should + * build query string first (using QString::arg() for string parameters) and then pass it to exec(), which will accept SQLite parameters binding. + * + * If the query doesn't return any interesting results (for example it's INSERT) and you don't care about errors, you can safely ignore results object. + * The result object is shared pointer, therefore it will delete itself if not used. + * + * Given C++11 you can initialize list with braces, like this: + * @code + * SqlQueryPtr results = db->exec("SELECT * FROM table WHERE c1 = ? AND c2 = ? AND c3 = ? AND c4 = ?", + * {45, 76, "test", 3.56}); + * @endcode + */ + virtual SqlQueryPtr exec(const QString& query, const QList &args, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query using named parameters. + * @param query Query to be executed. Parameter placeholders can be either of: :param, \@param, just don't mix different types in single query. + * @param args Map of parameter name and the value assigned to it. + * @param flags Execution flags. See exec() for setails. + * @return Execution results. + * + * Given C++11 you can initialize hash map with braces, like this: + * @code + * SqlQueryPtr results = db->exec("SELECT * FROM table WHERE id = :userId AND name = :firstName", + * { + * {":userId", 45}, + * {":firstName", "John"} + * }); + * @endcode + * + * @overload + */ + virtual SqlQueryPtr exec(const QString& query, const QHash& args, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query. + * @overload + */ + virtual SqlQueryPtr exec(const QString &query, Db::Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query. + * @overload + */ + virtual SqlQueryPtr exec(const QString &query, const QVariant &arg) = 0; + + /** + * @brief Executes SQL query. + * @overload + */ + virtual SqlQueryPtr exec(const QString &query, std::initializer_list argList) = 0; + + /** + * @brief Executes SQL query. + * @overload + */ + virtual SqlQueryPtr exec(const QString &query, std::initializer_list> argMap) = 0; + + /** + * @brief Executes SQL query asynchronously using list of parameters. + * @param query Query to be executed. Parameter placeholders can be either of: ?, :param, \@param, just don't mix different types in single query. + * @param args List of parameter values to bind. + * @param resultsHandler Function (can be lambda) to handle results. The function has to accept single SqlQueryPtr object and return nothing. + * @param flags Execution flags. See exec() for setails. + * + * Asynchronous execution takes place in another thread. Once the execution is finished, the results handler function is called. + * + * Example: + * @code + * db->asyncExec("SELECT * FROM table WHERE col = ?", {5}, [=](SqlQueryPtr results) + * { + * qDebug() << "Received" << results->rowCount() << "rows in results."; + * }); + * @endcode + */ + virtual void asyncExec(const QString& query, const QList& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously using named parameters. + * @param query Query to be executed. Parameter placeholders can be either of: :param, \@param, just don't mix different types in single query. + * @param args Map of parameter name and the value assigned to it. + * @param resultsHandler Function (can be lambda) to handle results. The function has to accept single SqlQueryPtr object and return nothing. + * @param flags Execution flags. See exec() for details. + * @return Asynchronous execution ID. + * @overload + */ + virtual void asyncExec(const QString& query, const QHash& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously. + * @param query Query to be executed. See exec() for details. + * @param resultsHandler Function (can be lambda) to handle results. The function has to accept single SqlQueryPtr object and return nothing. + * @param flags Execution flags. See exec() for details. + * @return Asynchronous execution ID. + * @overload + */ + virtual void asyncExec(const QString& query, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously using list of parameters. + * @param query Query to be executed. Parameter placeholders can be either of: ?, :param, \@param, just don't mix different types in single query. + * @param args List of parameter values to bind. + * @param flags Execution flags. See exec() for setails. + * @return Asynchronous execution ID. + * + * Asynchronous execution takes place in another thread. Once the execution is finished, the results is provided + * with asyncExecFinished() signal. You should get the ID from results of this method and compare it with ID + * from the signal, so when it matches, it means that the results object from signal is the answer to this execution. + * + * It's recommended to use method version which takes function pointer for results handing, as it's more resiliant to errors in the code. + * + * Given C++11 you can initialize list with braces, like this: + * @code + * int asyncId = db->asyncExec("SELECT * FROM table WHERE c1 = ? AND c2 = ? AND c3 = ? AND c4 = ?", + * {45, 76, "test", 3.56}); + * @endcode + */ + virtual quint32 asyncExec(const QString& query, const QList& args, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously using named parameters. + * @param query Query to be executed. Parameter placeholders can be either of: :param, \@param, just don't mix different types in single query. + * @param args Map of parameter name and the value assigned to it. + * @param flags Execution flags. See exec() for details. + * @return Asynchronous execution ID. + * @overload + * + * It's recommended to use method version which takes function pointer for results handing, as it's more resiliant to errors in the code. + */ + virtual quint32 asyncExec(const QString& query, const QHash& args, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously. + * @param query Query to be executed. See exec() for details. + * @param flags Execution flags. See exec() for details. + * @return Asynchronous execution ID. + * @overload + * + * It's recommended to use method version which takes function pointer for results handing, as it's more resiliant to errors in the code. + */ + virtual quint32 asyncExec(const QString& query, Flags flags = Flag::NONE) = 0; + + virtual SqlQueryPtr prepare(const QString& query) = 0; + + /** + * @brief Begins SQL transaction. + * @return true on success, or false on failure. + * + * This method uses basic "BEGIN" statement to begin transaction, therefore recurrent transactions are not supported. + * This is because SQLite2 doesn't support "SAVEPOINT" and this is the common interface for all SQLite versions. + */ + virtual bool begin() = 0; + + /** + * @brief Commits SQL transaction. + * @return true on success, or false otherwise. + */ + virtual bool commit() = 0; + + /** + * @brief Rolls back the transaction. + * @return true on success, or false otherwise (i.e. there was no transaction open, there was a connection problem, etc). + */ + virtual bool rollback() = 0; + + /** + * @brief Interrupts current execution asynchronously. + * + * It's almost the same as interrupt(), except it returns immediately, instead of waiting for the interruption to finish. + * In case of some heavy queries the interruption process might take a little while. + */ + virtual void asyncInterrupt() = 0; + + /** + * @brief Checks if the database is readable at the moment. + * @return true if the database is readable, or false otherwise. + * + * The database can be in 3 states: not locked, locked for reading or locked for writing. + * If it's locked for writing, than it's not readable and this method will return false. + * If it's locked for reading or not locked at all, then this method will return true. + * Database can be locked by other threads executing their queries on the database. + */ + virtual bool isReadable() = 0; + + /** + * @brief Checks if the database is writable at the moment. + * @return true if the database is writable, or false otherwise. + * + * The database can be in 3 states: not locked, locked for reading or locked for writing. + * If it's locked for writing (by other thread) or reading, than it's not writable and this method will return false. + * If it's not locked at all, then this method will return true. + * Database can be locked by other threads executing their queries on the database. + */ + virtual bool isWritable() = 0; + + /** + * @brief Tells if the database is valid for operating on it. + * @return true if the databse is valid, false otherwise. + * + * A valid database is the one that has valid path and driver plugin support loaded. + * Invalid database is the one that application failed to load. Those are marked with the exclamation icon on the UI. + */ + virtual bool isValid() const = 0; + + /** + * @brief Attaches given database to this database. + * @param otherDb Other registered database object. + * @param silent If true, no errors or warnings will be reported to the NotifyManager (they will still appear in logs). + * @return Name of the attached database (it's not the symbolic name of the other database, it's a name you would use in ATTACH 'name' statement). + * + * This is convinent method to attach other registered databases to this database. It generates attached database name, so it doesn't conflict + * with other - already attached - database names, attaches the database with that name and returns that name to you, so you can refer to it in queries. + */ + virtual QString attach(Db* otherDb, bool silent = false) = 0; + + /** + * @brief Attaches given database to this database using guarded attach. + * @param otherDb Other registered database object. + * @param silent If true, no errors or warnings will be reported to the NotifyManager (they will still appear in logs). + * @return Guarded attach instance with the name of the attached database inside. + * + * The guarded attach automatically detaches attached database when the attach guard is destroyed (goes out of scope). + * The AttachGuard is in fact a QSharedPointer, so you can pass it by value to other functions prolong attchment. + */ + virtual AttachGuard guardedAttach(Db* otherDb, bool silent = false) = 0; + + /** + * @brief Detaches given database from this database. + * @param otherDb Other registered database object. + * + * If the otherDb is not attached, this method does nothing. Otherwise it calls DETACH statement using the attach name generated before by attach(). + * You don't have to provide the attach name, as Db class remembers those names internally. + */ + virtual void detach(Db* otherDb) = 0; + + /** + * @brief Detaches all attached databases. + * + * Detaches all attached databases. This includes only databases attached with attach(). Databases attached with manual ATTACH query execution + * will not be detached. + */ + virtual void detachAll() = 0; + + /** + * @brief Gets attached databases. + * @return Table of attached databases and the attach names used to attach them. + * + * This method returns only databases attached with attach() method. + */ + virtual const QHash& getAttachedDatabases() = 0; + + /** + * @brief Gets all attached databases. + * @return Set of attach names. + * + * This method returns all attached database names (the attach names), including both those from attach() and manual ATTACH query execution. + */ + virtual QSet getAllAttaches() = 0; + + /** + * @brief Generates unique name for object to be created in the database. + * @param attachedDbName Optional attach name, so the name will be in context of that database. + * @return Unique object name. + * + * Queries database for all existing objects and then generates name that is not on that list. + * The generated name is a random string of length 16. + */ + virtual QString getUniqueNewObjectName(const QString& attachedDbName = QString()) = 0; + + /** + * @brief Gets last error string from database driver. + * @return Last encountered error. + * + * Result of this method is determinated by DbPlugin. + */ + virtual QString getErrorText() = 0; + + /** + * @brief Gets last error code from database driver. + * @return Code of last encountered error. + * + * Result of this method is determinated by DbPlugin. + */ + virtual int getErrorCode() = 0; + + /** + * @brief Gets database type label. + * @return Database type label. + * + * The database type label is used on UI to tell user what database it is (SQLite 3, SQLite 2, Encrypted SQLite 3, etc). + * This is defined by DbPlugin. + */ + virtual QString getTypeLabel() = 0; + + /** + * @brief Initializes resources once the all derived Db classes are constructed. + * @return true on success, false on failure. + * + * It's called just after this object was created. Implementation of this method can call virtual methods, which was a bad idea + * to do in constructor (because of how it's works in C++, if you didn't know). + * + * It usually queries database for it's version, etc. + */ + virtual bool initAfterCreated() = 0; + + /** + * @brief Deregisters custom SQL function from this database. + * @param name Name of the function. + * @param argCount Number of arguments accepted by the function (-1 for undefined). + * @return true if deregistering was successful, or false otherwise. + * + * @see FunctionManager + */ + virtual bool deregisterFunction(const QString& name, int argCount) = 0; + + /** + * @brief Registers scalar custom SQL function. + * @param name Name of the function. + * @param argCount Number of arguments accepted by the function (-1 for undefined). + * @return true on success, false on failure. + * + * Scalar functions are evaluated for each row and their result is used in place of function invokation. + * Example of SQLite built-in scalar function is abs(), or length(). + * + * This method is used only to let the database know, that the given function exists in FunctionManager and we want it to be visible + * in this database's context. When the function is called from SQL query, then the function execution is delegated to the FunctionManager. + * + * For details about usage of custom SQL functions see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions + * + * @see FunctionManager + */ + virtual bool registerScalarFunction(const QString& name, int argCount) = 0; + + /** + * @brief Registers aggregate custom SQL function. + * @param name Name of the function. + * @param argCount Number of arguments accepted by the function (-1 for undefined). + * @return true on success, false on failure. + * + * Aggregate functions are used to aggregate many rows into single row. They are common in queries with GROUP BY statements. + * The aggregate function in SQLite is actually implemented by 2 functions - one for executing per each row (and which doesn't return any result yet, + * just collects the data) and then the second function, executed at the end. The latter one must return the result, which becomes the result + * of aggregate function. + * + * Aggregate functions in SQLiteStudio are almost the same as in SQLite itself, except SQLiteStudio has also a third function, which is called + * at the very begining, before the first "per step" function is called. It's used to initialize anything that the step function might need. + * + * This method is used only to let the database know, that the given function exists in FunctionManager and we want it to be visible + * in this database's context. When the function is called from SQL query, then the function execution is delegated to the FunctionManager. + * + * For details about usage of custom SQL functions see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions + * + * @see FunctionManager + */ + virtual bool registerAggregateFunction(const QString& name, int argCount) = 0; + + /** + * @brief Registers a collation sequence implementation in the database. + * @param name Name of the collation. + * @return true on success, false on failure. + * + * Collations are not supported by SQLite 2, so this method will always fail for those databases. + * + * Collations are handled by CollationManager. Each collation managed by the manager has a code implemented to return -1, 0 or 1 + * when comparing 2 values in the database in order to sort query results. The name passed to this method is a name of the collation + * as it is used in SQL queries and also the same name must be used when defining collation in Collations editor window. + * + * For details about usage of custom collations see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_collations + * + * @see CollationManager + */ + virtual bool registerCollation(const QString& name) = 0; + + /** + * @brief Deregisters previously registered collation from this database. + * @param name Collation name. + * @return true on success, false on failure. + * + * See registerCollation() for details on custom collations. + */ + virtual bool deregisterCollation(const QString& name) = 0; + + signals: + /** + * @brief Emitted when the connection to the database was established. + */ + void connected(); + + /** + * @brief Emitted after connection to the database was closed. + */ + void disconnected(); + + /** + * @brief Emitted when other database was attached to this datbase. + * @param db Other database that was attached. + * + * This is emitted only when the database was attached with attach() call. + * Manual "ATTACH" query execution doesn't cause this signal to be emitted. + */ + void attached(Db* db); + + /** + * @brief Emitted when other database was detached from this datbase. + * @param db Other database that was detached. + * + * This is emitted only when the database was detached with detach() call. + * Manual "DETACH" query execution doesn't cause this signal to be emitted. + */ + void detached(Db* db); + + //void attached(QString db); // TODO emit when called by user's sql + //void detached(QString db); // TODO emit when called by user's sql + + /** + * @brief Emitted when the asynchronous execution was finished. + * @param asyncId Asynchronous ID. + * @param results Results from query execution. + * + * This signal is emitted only when no handler function was passed to asyncExec(). + * It's emitted, so the results can be handled. + * Always test \p asyncId if it's equal to ID returned from asyncExec(). + */ + void asyncExecFinished(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief idle Database became idle and awaits for instructions. + * + * This signal is emited after async execution has finished. + * It is important to re-check isWritable() or isReadable() + * in any slot connected to this signal, because some other slot + * called before currently processed slot could already order + * another async execution. + */ + void idle(); + + /** + * @brief Emitted when any database object (table, index, trigger, or view) was just deleted from this database. + * @param database Database (attach) name from which the object was deleted. Usually the "main". + * @param name Name of the object deleted. + * @param type Type of the object deleted. + * + * This signal covers only deletions made by this database of course. Deletions made by any other application + * are not announced by this signal (as this is impossible to detect it just like that). + */ + void dbObjectDeleted(const QString& database, const QString& name, DbObjectType type); + + /** + * @brief Emitted just before disconnecting and user can deny it. + * @param disconnectingDenied If set to true by anybody, then disconnecting is aborted. + */ + void aboutToDisconnect(bool& disconnectingDenied); + + public slots: + /** + * @brief Opens connection to the database. + * @return true on success, false on error. + * + * Emits connected() only on success. + */ + virtual bool open() = 0; + + /** + * @brief Closes connection to the database. + * @return true on success, false on error. + * + * Emits disconnected() only on success (i.e. db was open before). + */ + virtual bool close() = 0; + + /** + * @brief Opens connection to the database quietly. + * @return true on success, false on error. + * + * Opens database, doesn't emit any signal. + */ + virtual bool openQuiet() = 0; + + /** + * @brief Opens connection to the database quietly, without applying any specific settings. + * @return true on success, false on error. + * + * Opens database, doesn't emit any signal. It also doesn't apply any pragmas, neither registers + * functions or collations. It should be used when you want to do some basic query on the database, + * like when you probe the database for being the correct database for this implementation (driver, etc). + * Actually, that's what DbPluginSqlite3 plugin (among others) use. + * + * To close database open with this method use closeQuiet(). + */ + virtual bool openForProbing() = 0; + + /** + * @brief Closes connection to the database quietly. + * @return true on success, false on error. + * + * Closes database, doesn't emit any signal. + */ + virtual bool closeQuiet() = 0; + + /** + * @brief Deregisters all funtions registered in the database and registers new (possibly the same) functions. + * + * This slot is called from openAndSetup() and then every time user modifies custom SQL functions and commits changes to them. + * It deregisters all functions registered before in this database and registers new functions, currently defined for + * this database. + * + * @see FunctionManager + */ + virtual void registerAllFunctions() = 0; + + /** + * @brief Deregisters all collations registered in the database and registers new (possibly the same) collations. + * + * This slot is called from openAndsetup() and then every time user modifies custom collations and commits changes to them. + */ + virtual void registerAllCollations() = 0; +}; + +QDataStream &operator<<(QDataStream &out, const Db* myObj); +QDataStream &operator>>(QDataStream &in, Db*& myObj); + +Q_DECLARE_METATYPE(Db*) +Q_DECLARE_OPERATORS_FOR_FLAGS(Db::Flags) + +class API_EXPORT Sqlite2ColumnDataTypeHelper +{ + public: + void setBinaryType(int columnIndex); + bool isBinaryColumn(int columnIndex) const; + void clearBinaryTypes(); + + private: + QSet binaryColumns; +}; + +#endif // DB_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h b/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h new file mode 100644 index 0000000..7b594ef --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h @@ -0,0 +1,97 @@ +#ifndef DBPLUGINOPTION_H +#define DBPLUGINOPTION_H + +#include +#include + +/** + * @brief Database plugin connection options. + * + * It is used to identify connection options that the DbPlugin implementation needs + * for the plugin to be configured by the user in the DbDialog. + * + * Single DbPluginOption represents in DbDialog: + *
    + *
  • single QLabel with text set to DbPluginOption::label,
  • + *
  • an input widget, that depends on DbPluginOption::type.
  • + *
+ * + * The input widget is different for different data type expected for the option. + * See DbPluginOption::Type for details. + * + * After user entered his values for options in DbDialog, they are passed + * to the DbPlugin::getInstance() and later to the Db::init(). Options are passed + * as key-value pairs, given the DbPluginOption::key and value specified by the user + * in DbDialog. + */ +struct DbPluginOption +{ + /** + * @brief Option data type + * + * Determinates what kind of input widget will be added to DbDialog. + */ + enum Type + { + STRING = 0, /**< QLineEdit will be added. */ + INT = 1, /**< QSpinBox will be added */ + BOOL = 2, /**< QCheckBox will be added */ + DOUBLE = 3, /**< QDoubleSpinBox will be added */ + FILE = 4, /**< QLineEdit will be added */ + PASSWORD = 5, /**< QLineEdit with value masking will be added */ + CHOICE = 6 /**< QComboBox will be added */ + }; + + /** + * @brief Name for the key in QHash collected from options in DbDialog and + * later passed to DbPlugin::getInstance(). + */ + QString key; + + /** + * @brief Label text to be used in DbDialog to inform user what is this option. + */ + QString label; + + /** + * @brief Optional tooltip to show for added widget. + */ + QString toolTip; + + /** + * @brief Optional placeholder text for QLineEdit widget. + */ + QString placeholderText; + + /** + * @brief List of values for QComboBox. + */ + QStringList choiceValues; + + /** + * @brief Default value to be set in the editor widget. + */ + QVariant defaultValue; + + /** + * @brief Indicates if the combobox should be read only or writable. + */ + bool choiceReadOnly = true; + + /** + * @brief Minimum value for numeric editors (double or int). + */ + QVariant minValue; + + /** + * @brief Maximum value for numeric editors (double or int). + */ + QVariant maxValue; + + /** + * @brief Expected data type for the option. + */ + Type type; +}; + +#endif // DBPLUGINOPTION_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite.h.autosave b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite.h.autosave new file mode 100644 index 0000000..9d11dac --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite.h.autosave @@ -0,0 +1,72 @@ +#ifndef DBSQLITE_H +#define DBSQLITE_H + +#include "db.h" +#include "../returncode.h" +#include "sqlerror.h" +#include "sqlresults.h" +#include "../dialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class AsyncQueryRunner; + +class DbSqlite : public Db +{ + Q_OBJECT + + public: + virtual ~DbSqlite(); + + static DbPtr getInstance(const QString &name, const QString& path, + const QString &options = QString::null); + + QString getName(); + QString getPath(); + quint8 getVersion(); + virtual QString driver() = 0; + Dialect getDialect(); + + quint32 asyncExec(const QString& query, const QVariant& arg1 = QVariant(), + const QVariant& arg2 = QVariant(), const QVariant& arg3 = QVariant()); + quint32 asyncExecStr(const QString& query, const QVariant& arg1 = QVariant(), + const QVariant& arg2 = QVariant(), const QVariant& arg3 = QVariant()); + quint32 asyncExecArgs(const QString& query, const QList& args); + quint32 asyncExecArgs(const QString& query, const QMap& args); + + void begin(); + bool commit(); + void rollback(); + QString getType(); + SqlError lastError(); + + protected: + Db(); + + void cleanUp(); + QString generateUniqueDbName(); + bool isOpenNoLock(); + quint32 asyncExec(AsyncQueryRunner* runner); + SqlResultsPtr execInternal(const QString& query, const QList& args, + bool singleCell); + SqlResultsPtr execInternal(const QString& query, const QMap& args, + bool singleCell); + bool init(); + + QSqlDatabase db; + quint8 version = 0; + + public slots: + bool openQuiet(); + bool closeQuiet(); + +}; + +#endif // DBSQLITE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp new file mode 100644 index 0000000..a4a8b73 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp @@ -0,0 +1,11 @@ +#include "dbsqlite3.h" + +DbSqlite3::DbSqlite3(const QString& name, const QString& path, const QHash& connOptions) : + AbstractDb3(name, path, connOptions) +{ +} + +DbSqlite3::DbSqlite3(const QString& name, const QString& path) : + DbSqlite3(name, path, QHash()) +{ +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h new file mode 100644 index 0000000..29db5a8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h @@ -0,0 +1,33 @@ +#ifndef DBSQLITE3_H +#define DBSQLITE3_H + +#include "abstractdb3.h" +#include "common/global.h" +#include "stdsqlite3driver.h" +#include + +STD_SQLITE3_DRIVER(Sqlite3, "SQLite 3",,) + +class API_EXPORT DbSqlite3 : public AbstractDb3 +{ + public: + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See AbstractDb for details. + * + * All values from this constructor are just passed to AbstractDb3 constructor. + */ + DbSqlite3(const QString& name, const QString& path, const QHash& connOptions); + + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @overload + */ + DbSqlite3(const QString& name, const QString& path); +}; + +#endif // DBSQLITE3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp new file mode 100644 index 0000000..e4810a1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp @@ -0,0 +1,336 @@ +#include "invaliddb.h" +#include "common/unused.h" +#include + +InvalidDb::InvalidDb(const QString& name, const QString& path, const QHash& connOptions) : + name(name), path(path), connOptions(connOptions) +{ +} + +bool InvalidDb::isOpen() +{ + return false; +} + +QString InvalidDb::getName() +{ + return name; +} + +QString InvalidDb::getPath() +{ + return path; +} + +quint8 InvalidDb::getVersion() +{ + return 0; +} + +Dialect InvalidDb::getDialect() +{ + return Dialect::Sqlite3; +} + +QString InvalidDb::getEncoding() +{ + return QString::null; +} + +QHash& InvalidDb::getConnectionOptions() +{ + return connOptions; +} + +void InvalidDb::setName(const QString& value) +{ + name = value; +} + +void InvalidDb::setPath(const QString& value) +{ + path = value; +} + +void InvalidDb::setConnectionOptions(const QHash& value) +{ + connOptions = value; +} + +void InvalidDb::setTimeout(int secs) +{ + timeout = secs; +} + +int InvalidDb::getTimeout() const +{ + return timeout; +} + +SqlQueryPtr InvalidDb::exec(const QString& query, const QList& args, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(flags); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, const QHash& args, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(flags); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, Db::Flags flags) +{ + UNUSED(query); + UNUSED(flags); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, const QVariant& arg) +{ + UNUSED(query); + UNUSED(arg); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, std::initializer_list argList) +{ + UNUSED(query); + UNUSED(argList); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, std::initializer_list > argMap) +{ + UNUSED(query); + UNUSED(argMap); + return SqlQueryPtr(); +} + +void InvalidDb::asyncExec(const QString& query, const QList& args, Db::QueryResultsHandler resultsHandler, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(resultsHandler); + UNUSED(flags); +} + +void InvalidDb::asyncExec(const QString& query, const QHash& args, Db::QueryResultsHandler resultsHandler, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(resultsHandler); + UNUSED(flags); +} + +void InvalidDb::asyncExec(const QString& query, Db::QueryResultsHandler resultsHandler, Db::Flags flags) +{ + UNUSED(query); + UNUSED(resultsHandler); + UNUSED(flags); +} + +quint32 InvalidDb::asyncExec(const QString& query, const QList& args, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(flags); + return 0; +} + +quint32 InvalidDb::asyncExec(const QString& query, const QHash& args, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(flags); + return 0; +} + +quint32 InvalidDb::asyncExec(const QString& query, Db::Flags flags) +{ + UNUSED(query); + UNUSED(flags); + return 0; +} + +SqlQueryPtr InvalidDb::prepare(const QString& query) +{ + UNUSED(query); + return SqlQueryPtr(); +} + +bool InvalidDb::begin() +{ + return false; +} + +bool InvalidDb::commit() +{ + return false; +} + +bool InvalidDb::rollback() +{ + return false; +} + +void InvalidDb::asyncInterrupt() +{ +} + +bool InvalidDb::isReadable() +{ + return false; +} + +bool InvalidDb::isWritable() +{ + return false; +} + +QString InvalidDb::attach(Db* otherDb, bool silent) +{ + UNUSED(otherDb); + UNUSED(silent); + return QString::null; +} + +AttachGuard InvalidDb::guardedAttach(Db* otherDb, bool silent) +{ + UNUSED(silent); + return AttachGuard::create(this, otherDb, QString::null); +} + +void InvalidDb::detach(Db* otherDb) +{ + UNUSED(otherDb); +} + +void InvalidDb::detachAll() +{ +} + +const QHash& InvalidDb::getAttachedDatabases() +{ + return attachedDbs; +} + +QSet InvalidDb::getAllAttaches() +{ + return QSet(); +} + +QString InvalidDb::getUniqueNewObjectName(const QString& attachedDbName) +{ + UNUSED(attachedDbName); + return QString::null; +} + +QString InvalidDb::getErrorText() +{ + return QString::null; +} + +int InvalidDb::getErrorCode() +{ + return 0; +} + +QString InvalidDb::getTypeLabel() +{ + return QStringLiteral("INVALID"); +} + +bool InvalidDb::initAfterCreated() +{ + return false; +} + +bool InvalidDb::deregisterFunction(const QString& name, int argCount) +{ + UNUSED(name); + UNUSED(argCount); + return false; +} + +bool InvalidDb::registerScalarFunction(const QString& name, int argCount) +{ + UNUSED(name); + UNUSED(argCount); + return false; +} + +bool InvalidDb::registerAggregateFunction(const QString& name, int argCount) +{ + UNUSED(name); + UNUSED(argCount); + return false; +} + +bool InvalidDb::registerCollation(const QString& name) +{ + UNUSED(name); + return false; +} + +bool InvalidDb::deregisterCollation(const QString& name) +{ + UNUSED(name); + return false; +} + +bool InvalidDb::open() +{ + return false; +} + +bool InvalidDb::close() +{ + return false; +} + +bool InvalidDb::openQuiet() +{ + return false; +} + +bool InvalidDb::openForProbing() +{ + return false; +} + +bool InvalidDb::closeQuiet() +{ + return false; +} + +void InvalidDb::registerAllFunctions() +{ +} + +void InvalidDb::registerAllCollations() +{ +} +QString InvalidDb::getError() const +{ + return error; +} + +void InvalidDb::setError(const QString& value) +{ + error = value; +} + + +void InvalidDb::interrupt() +{ +} + +bool InvalidDb::isValid() const +{ + return false; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h new file mode 100644 index 0000000..759aa4c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h @@ -0,0 +1,81 @@ +#ifndef INVALIDDB_H +#define INVALIDDB_H + +#include "db/db.h" + +class API_EXPORT InvalidDb : public Db +{ + public: + InvalidDb(const QString& name, const QString& path, const QHash& connOptions); + + bool isOpen(); + QString getName(); + QString getPath(); + quint8 getVersion(); + Dialect getDialect(); + QString getEncoding(); + QHash& getConnectionOptions(); + void setName(const QString& value); + void setPath(const QString& value); + void setConnectionOptions(const QHash& value); + void setTimeout(int secs); + int getTimeout() const; + SqlQueryPtr exec(const QString& query, const QList& args, Flags flags); + SqlQueryPtr exec(const QString& query, const QHash& args, Flags flags); + SqlQueryPtr exec(const QString& query, Db::Flags flags); + SqlQueryPtr exec(const QString& query, const QVariant& arg); + SqlQueryPtr exec(const QString& query, std::initializer_list argList); + SqlQueryPtr exec(const QString& query, std::initializer_list > argMap); + void asyncExec(const QString& query, const QList& args, QueryResultsHandler resultsHandler, Flags flags); + void asyncExec(const QString& query, const QHash& args, QueryResultsHandler resultsHandler, Flags flags); + void asyncExec(const QString& query, QueryResultsHandler resultsHandler, Flags flags); + quint32 asyncExec(const QString& query, const QList& args, Flags flags); + quint32 asyncExec(const QString& query, const QHash& args, Flags flags); + quint32 asyncExec(const QString& query, Flags flags); + SqlQueryPtr prepare(const QString& query); + bool begin(); + bool commit(); + bool rollback(); + void asyncInterrupt(); + bool isReadable(); + bool isWritable(); + QString attach(Db* otherDb, bool silent); + AttachGuard guardedAttach(Db* otherDb, bool silent); + void detach(Db* otherDb); + void detachAll(); + const QHash& getAttachedDatabases(); + QSet getAllAttaches(); + QString getUniqueNewObjectName(const QString& attachedDbName); + QString getErrorText(); + int getErrorCode(); + QString getTypeLabel(); + bool initAfterCreated(); + bool deregisterFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount); + bool registerAggregateFunction(const QString& name, int argCount); + bool registerCollation(const QString& name); + bool deregisterCollation(const QString& name); + void interrupt(); + bool isValid() const; + QString getError() const; + void setError(const QString& value); + + public slots: + bool open(); + bool close(); + bool openQuiet(); + bool openForProbing(); + bool closeQuiet(); + void registerAllFunctions(); + void registerAllCollations(); + + private: + QString name; + QString path; + QHash connOptions; + int timeout = 0; + QHash attachedDbs; + QString error; +}; + +#endif // INVALIDDB_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp new file mode 100644 index 0000000..c840947 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp @@ -0,0 +1,784 @@ +#include "queryexecutor.h" +#include "sqlerrorresults.h" +#include "sqlerrorcodes.h" +#include "services/dbmanager.h" +#include "db/sqlerrorcodes.h" +#include "services/notifymanager.h" +#include "queryexecutorsteps/queryexecutoraddrowids.h" +#include "queryexecutorsteps/queryexecutorcolumns.h" +#include "queryexecutorsteps/queryexecutorparsequery.h" +#include "queryexecutorsteps/queryexecutorattaches.h" +#include "queryexecutorsteps/queryexecutorcountresults.h" +#include "queryexecutorsteps/queryexecutorexecute.h" +#include "queryexecutorsteps/queryexecutorcellsize.h" +#include "queryexecutorsteps/queryexecutorlimit.h" +#include "queryexecutorsteps/queryexecutororder.h" +#include "queryexecutorsteps/queryexecutorwrapdistinctresults.h" +#include "queryexecutorsteps/queryexecutordatasources.h" +#include "queryexecutorsteps/queryexecutorexplainmode.h" +#include "queryexecutorsteps/queryexecutorreplaceviews.h" +#include "queryexecutorsteps/queryexecutordetectschemaalter.h" +#include "queryexecutorsteps/queryexecutorvaluesmode.h" +#include "common/unused.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO modify all executor steps to use rebuildTokensFromContents() method, instead of replacing tokens manually. + +QueryExecutor::QueryExecutor(Db* db, const QString& query, QObject *parent) : + QObject(parent) +{ + context = new Context(); + originalQuery = query; + setDb(db); + setAutoDelete(false); + + connect(this, SIGNAL(executionFinished(SqlQueryPtr)), this, SLOT(cleanupAfterExecFinished(SqlQueryPtr))); + connect(this, SIGNAL(executionFailed(int,QString)), this, SLOT(cleanupAfterExecFailed(int,QString))); + connect(DBLIST, SIGNAL(dbAboutToBeUnloaded(Db*, DbPlugin*)), this, SLOT(cleanupBeforeDbDestroy(Db*, DbPlugin*))); +} + +QueryExecutor::~QueryExecutor() +{ + delete context; + context = nullptr; +} + +void QueryExecutor::setupExecutionChain() +{ + executionChain << new QueryExecutorParseQuery("initial") + << new QueryExecutorDetectSchemaAlter() + << new QueryExecutorExplainMode() + << new QueryExecutorValuesMode() + << new QueryExecutorAttaches() // needs to be at the begining, because columns needs to know real databases + << new QueryExecutorParseQuery("after Attaches") + << new QueryExecutorDataSources() + << new QueryExecutorReplaceViews() + << new QueryExecutorParseQuery("after ReplaceViews") + << new QueryExecutorAddRowIds() + << new QueryExecutorParseQuery("after AddRowIds") + << new QueryExecutorColumns() + << new QueryExecutorParseQuery("after Columns") + //<< new QueryExecutorColumnAliases() + << new QueryExecutorOrder() + << new QueryExecutorWrapDistinctResults() + << new QueryExecutorParseQuery("after WrapDistinctResults") + << new QueryExecutorCellSize() + << new QueryExecutorCountResults() + << new QueryExecutorParseQuery("after Order") + << new QueryExecutorLimit() + << new QueryExecutorParseQuery("after Limit") + << new QueryExecutorExecute(); + + foreach (QueryExecutorStep* step, executionChain) + step->init(this, context); +} + +void QueryExecutor::clearChain() +{ + foreach (QueryExecutorStep* step, executionChain) + delete step; + + executionChain.clear(); +} + +void QueryExecutor::executeChain() +{ + // Go through all remaining steps + bool result; + foreach (QueryExecutorStep* currentStep, executionChain) + { + if (interrupted) + { + stepFailed(currentStep); + return; + } + + result = currentStep->exec(); + if (!result) + { + stepFailed(currentStep); + return; + } + } + + // We're done. + clearChain(); + + executionMutex.lock(); + executionInProgress = false; + executionMutex.unlock(); + + emit executionFinished(context->executionResults); +} + +void QueryExecutor::stepFailed(QueryExecutorStep* currentStep) +{ + qDebug() << "Smart execution failed at step" << currentStep->metaObject()->className() << currentStep->objectName() + << "\nUsing simple execution method."; + + clearChain(); + + if (interrupted) + { + executionInProgress = false; + emit executionFailed(SqlErrorCode::INTERRUPTED, tr("Execution interrupted.")); + return; + } + + // Clear anything meaningful set up for smart execution - it's not valid anymore and misleads results for simple method + context->rowIdColumns.clear(); + + executeSimpleMethod(); +} + +void QueryExecutor::cleanupAfterExecFinished(SqlQueryPtr results) +{ + UNUSED(results); + cleanup(); +} + +void QueryExecutor::cleanupAfterExecFailed(int code, QString errorMessage) +{ + UNUSED(code); + UNUSED(errorMessage); + cleanup(); +} + +void QueryExecutor::cleanupBeforeDbDestroy(Db* dbToBeUnloaded, DbPlugin* plugin) +{ + UNUSED(plugin); + if (!dbToBeUnloaded || dbToBeUnloaded != db) + return; + + setDb(nullptr); + context->executionResults.clear(); +} + +void QueryExecutor::setQuery(const QString& query) +{ + originalQuery = query; +} + +void QueryExecutor::exec(Db::QueryResultsHandler resultsHandler) +{ + if (!db) + { + qWarning() << "Database is not set in QueryExecutor::exec()."; + return; + } + + if (!db->isOpen()) + { + error(SqlErrorCode::DB_NOT_OPEN, tr("Database is not open.")); + return; + } + + // Get exclusive flow for execution on this query executor + executionMutex.lock(); + if (executionInProgress) + { + error(SqlErrorCode::QUERY_EXECUTOR_ERROR, tr("Only one query can be executed simultaneously.")); + executionMutex.unlock(); + return; + } + executionInProgress = true; + executionMutex.unlock(); + + this->resultsHandler = resultsHandler; + + if (asyncMode) + QThreadPool::globalInstance()->start(this); + else + run(); +} + +void QueryExecutor::run() +{ + execInternal(); +} + +void QueryExecutor::execInternal() +{ + simpleExecution = false; + interrupted = false; + + if (resultsCountingAsyncId != 0) + { + resultsCountingAsyncId = 0; + db->interrupt(); + } + + // Reset context + delete context; + context = new Context(); + context->processedQuery = originalQuery; + context->explainMode = explainMode; + context->skipRowCounting = skipRowCounting; + context->noMetaColumns = noMetaColumns; + context->resultsHandler = resultsHandler; + context->preloadResults = preloadResults; + + // Start the execution + setupExecutionChain(); + executeChain(); +} + +void QueryExecutor::interrupt() +{ + if (!db) + { + qWarning() << "Called interrupt() on empty db in QueryExecutor."; + return; + } + + interrupted = true; + db->asyncInterrupt(); +} + +void QueryExecutor::countResults() +{ + if (context->skipRowCounting) + return; + + if (context->countingQuery.isEmpty()) // simple method doesn't provide that + return; + + if (asyncMode) + { + // Start asynchronous results counting query + resultsCountingAsyncId = db->asyncExec(context->countingQuery, context->queryParameters); + } + else + { + SqlQueryPtr results = db->exec(context->countingQuery, context->queryParameters); + context->totalRowsReturned = results->getSingleCell().toLongLong(); + context->totalPages = (int)qCeil(((double)(context->totalRowsReturned)) / ((double)getResultsPerPage())); + + emit resultsCountingFinished(context->rowsAffected, context->totalRowsReturned, context->totalPages); + + if (results->isError()) + { + notifyError(tr("An error occured while executing the count(*) query, thus data paging will be disabled. Error details from the database: %1") + .arg(results->getErrorText())); + } + } +} + +qint64 QueryExecutor::getLastExecutionTime() const +{ + return context->executionTime; +} + +qint64 QueryExecutor::getRowsAffected() const +{ + return context->rowsAffected; +} + +qint64 QueryExecutor::getTotalRowsReturned() const +{ + return context->totalRowsReturned; +} + +SqliteQueryType QueryExecutor::getExecutedQueryType(int index) +{ + if (context->parsedQueries.size() == 0) + return SqliteQueryType::UNDEFINED; + + if (index < 0) + return context->parsedQueries.last()->queryType; + + if (index < context->parsedQueries.size()) + return context->parsedQueries[index]->queryType; + + return SqliteQueryType::UNDEFINED; +} + +QSet QueryExecutor::getSourceTables() const +{ + return context->sourceTables; +} + +int QueryExecutor::getTotalPages() const +{ + return context->totalPages; +} + +QList QueryExecutor::getResultColumns() const +{ + return context->resultColumns; +} + +QList QueryExecutor::getRowIdResultColumns() const +{ + return context->rowIdColumns; +} + +int QueryExecutor::getMetaColumnCount() const +{ + int count = 0; + for (ResultRowIdColumnPtr rowIdCol : context->rowIdColumns) + count += rowIdCol->queryExecutorAliasToColumn.size(); + + return count; +} + +QSet QueryExecutor::getEditionForbiddenGlobalReasons() const +{ + return context->editionForbiddenReasons; +} + +void QueryExecutor::setParam(const QString& name, const QVariant& value) +{ + context->queryParameters[name] = value; +} + +void QueryExecutor::arg(const QVariant& value) +{ + QVariant::Type type = value.type(); + switch (type) + { + case QVariant::Bool: + case QVariant::Int: + originalQuery = originalQuery.arg(value.toInt()); + break; + case QVariant::LongLong: + originalQuery = originalQuery.arg(value.toLongLong()); + break; + case QVariant::UInt: + originalQuery = originalQuery.arg(value.toUInt()); + break; + case QVariant::ULongLong: + originalQuery = originalQuery.arg(value.toULongLong()); + break; + case QVariant::Double: + originalQuery = originalQuery.arg(value.toDouble()); + break; + case QVariant::String: + { + if (value.canConvert(QVariant::LongLong)) + originalQuery = originalQuery.arg(value.toLongLong()); + else if (value.canConvert(QVariant::Double)) + originalQuery = originalQuery.arg(value.toDouble()); + else + originalQuery = originalQuery.arg("'"+value.toString().replace("'", "''")+"'"); + + break; + } + default: + return; + } +} + +void QueryExecutor::exec(const QString& query) +{ + setQuery(query); + exec(); +} + +void QueryExecutor::dbAsyncExecFinished(quint32 asyncId, SqlQueryPtr results) +{ + if (handleRowCountingResults(asyncId, results)) + return; + + if (!simpleExecution) + return; + + if (this->asyncId == 0) + return; + + if (this->asyncId != asyncId) + return; + + this->asyncId = 0; + + simpleExecutionFinished(results); +} + +void QueryExecutor::executeSimpleMethod() +{ + simpleExecution = true; + context->editionForbiddenReasons << EditionForbiddenReason::SMART_EXECUTION_FAILED; + simpleExecutionStartTime = QDateTime::currentMSecsSinceEpoch(); + asyncId = db->asyncExec(originalQuery, context->queryParameters, Db::Flag::PRELOAD); +} + +void QueryExecutor::simpleExecutionFinished(SqlQueryPtr results) +{ + if (results->isError()) + { + executionMutex.lock(); + executionInProgress = false; + executionMutex.unlock(); + error(results->getErrorCode(), results->getErrorText()); + return; + } + + if (simpleExecIsSelect()) + context->countingQuery = "SELECT count(*) AS cnt FROM ("+originalQuery+");"; + else + context->rowsCountingRequired = true; + + ResultColumnPtr resCol; + context->resultColumns.clear(); + foreach (const QString& colName, results->getColumnNames()) + { + resCol = ResultColumnPtr::create(); + resCol->displayName = colName; + context->resultColumns << resCol; + } + + context->executionTime = QDateTime::currentMSecsSinceEpoch() - simpleExecutionStartTime; + context->rowsAffected = results->rowsAffected(); + context->totalRowsReturned = 0; + + executionMutex.lock(); + executionInProgress = false; + executionMutex.unlock(); + if (context->resultsHandler) + { + context->resultsHandler(results); + context->resultsHandler = nullptr; + } + + notifyWarn(tr("SQLiteStudio was unable to extract metadata from the query. Results won't be editable.")); + + emit executionFinished(results); +} + +bool QueryExecutor::simpleExecIsSelect() +{ + TokenList tokens = Lexer::tokenize(originalQuery, db->getDialect()); + tokens.trim(); + + // First check if it's explicit "SELECT" or "VALUES" (the latter one added in SQLite 3.8.4). + TokenPtr token = tokens.first(); + QString upper = token->value.toUpper(); + if (token->type == Token::KEYWORD && (upper == "SELECT" || upper == "VALUES")) + return true; + + // Now it's only possible to be a SELECT if it starts with "WITH" statement. + if (token->type != Token::KEYWORD || upper != "WITH") + return false; + + // Go through all tokens and find which one appears first (exclude contents indise parenthesis, + // cause there will always be a SELECT for Common Table Expression). + int depth = 0; + foreach (token, tokens) + { + switch (token->type) + { + case Token::PAR_LEFT: + depth--; + break; + case Token::PAR_RIGHT: + depth++; + break; + case Token::KEYWORD: + { + if (depth > 0) + break; + + upper = token->value.toUpper(); + if (upper == "SELECT") + return true; + + if (upper == "UPDATE" || upper == "DELETE" || upper == "INSERT") + return false; + + break; + } + default: + break; + } + } + return false; +} + +void QueryExecutor::cleanup() +{ + Db* attDb = nullptr; + foreach (const QString& attDbName, context->dbNameToAttach.leftValues()) + { + attDb = DBLIST->getByName(attDbName, Qt::CaseInsensitive); + if (attDbName.isNull()) + { + qWarning() << "Could not find db by name for cleanup after execution in QueryExecutor. Searched for db named:" << attDbName; + continue; + } + db->detach(attDb); + } +} + +bool QueryExecutor::handleRowCountingResults(quint32 asyncId, SqlQueryPtr results) +{ + if (resultsCountingAsyncId == 0) + return false; + + if (resultsCountingAsyncId != asyncId) + return false; + + if (isExecutionInProgress()) // shouldn't be true, but just in case + return false; + + resultsCountingAsyncId = 0; + + context->totalRowsReturned = results->getSingleCell().toLongLong(); + context->totalPages = (int)qCeil(((double)(context->totalRowsReturned)) / ((double)getResultsPerPage())); + + emit resultsCountingFinished(context->rowsAffected, context->totalRowsReturned, context->totalPages); + + if (results->isError()) + { + notifyError(tr("An error occured while executing the count(*) query, thus data paging will be disabled. Error details from the database: %1") + .arg(results->getErrorText())); + } + + return true; +} +bool QueryExecutor::getNoMetaColumns() const +{ + return noMetaColumns; +} + +void QueryExecutor::setNoMetaColumns(bool value) +{ + noMetaColumns = value; +} + +SqlQueryPtr QueryExecutor::getResults() const +{ + return context->executionResults; +} + +bool QueryExecutor::wasSchemaModified() const +{ + return context->schemaModified; +} + +QList QueryExecutor::resolveColumnTypes(Db* db, QList& columns, bool noDbLocking) +{ + QSet
tables; + for (ResultColumnPtr col : columns) + tables << Table(col->database, col->table); + + SchemaResolver resolver(db); + resolver.setNoDbLocking(noDbLocking); + + QHash parsedTables; + SqliteCreateTablePtr createTable; + for (const Table& t : tables) + { + createTable = resolver.getParsedObject(t.getDatabase(), t.getTable(), SchemaResolver::TABLE).dynamicCast(); + if (!createTable) + { + qWarning() << "Could not resolve columns of table" << t.getTable() << "while quering datatypes for queryexecutor columns."; + continue; + } + parsedTables[t] = createTable; + } + + QList datatypeList; + Table t; + SqliteCreateTable::Column* parsedCol = nullptr; + for (ResultColumnPtr col : columns) + { + t = Table(col->database, col->table); + if (!parsedTables.contains(t)) + { + datatypeList << DataType(); + continue; + } + + parsedCol = parsedTables[t]->getColumn(col->column); + if (!parsedCol || !parsedCol->type) + { + datatypeList << DataType(); + continue; + } + + datatypeList << parsedCol->type->toDataType(); + } + return datatypeList; +} + +bool QueryExecutor::getAsyncMode() const +{ + return asyncMode; +} + +void QueryExecutor::setAsyncMode(bool value) +{ + asyncMode = value; +} + +void QueryExecutor::setPreloadResults(bool value) +{ + preloadResults = value; +} + +bool QueryExecutor::getExplainMode() const +{ + return explainMode; +} + +void QueryExecutor::setExplainMode(bool value) +{ + explainMode = value; +} + + +void QueryExecutor::error(int code, const QString& text) +{ + emit executionFailed(code, text); +} + +Db* QueryExecutor::getDb() const +{ + return db; +} + +void QueryExecutor::setDb(Db* value) +{ + if (db) + disconnect(db, SIGNAL(asyncExecFinished(quint32,SqlQueryPtr)), this, SLOT(dbAsyncExecFinished(quint32,SqlQueryPtr))); + + db = value; + + if (db) + connect(db, SIGNAL(asyncExecFinished(quint32,SqlQueryPtr)), this, SLOT(dbAsyncExecFinished(quint32,SqlQueryPtr))); +} + +bool QueryExecutor::getSkipRowCounting() const +{ + return skipRowCounting; +} + +void QueryExecutor::setSkipRowCounting(bool value) +{ + skipRowCounting = value; +} + +QString QueryExecutor::getOriginalQuery() const +{ + return originalQuery; +} + +int qHash(QueryExecutor::EditionForbiddenReason reason) +{ + return static_cast(reason); +} + +int qHash(QueryExecutor::ColumnEditionForbiddenReason reason) +{ + return static_cast(reason); +} + +int QueryExecutor::getDataLengthLimit() const +{ + return dataLengthLimit; +} + +void QueryExecutor::setDataLengthLimit(int value) +{ + dataLengthLimit = value; +} + +bool QueryExecutor::isRowCountingRequired() const +{ + return context->rowsCountingRequired; +} + +QString QueryExecutor::getCountingQuery() const +{ + return context->countingQuery; +} + +int QueryExecutor::getResultsPerPage() const +{ + return resultsPerPage; +} + +void QueryExecutor::setResultsPerPage(int value) +{ + resultsPerPage = value; +} + +int QueryExecutor::getPage() const +{ + return page; +} + +void QueryExecutor::setPage(int value) +{ + page = value; +} + +bool QueryExecutor::isExecutionInProgress() +{ + QMutexLocker executionLock(&executionMutex); + return executionInProgress; +} + +QueryExecutor::Sort::Sort() +{ +} + +QueryExecutor::Sort::Sort(QueryExecutor::Sort::Order order, int column) + : order(order), column(column) +{ +} + +QueryExecutor::Sort::Sort(Qt::SortOrder order, int column) + : column(column) +{ + switch (order) + { + case Qt::AscendingOrder: + this->order = ASC; + break; + case Qt::DescendingOrder: + this->order = DESC; + break; + default: + this->order = NONE; + qWarning() << "Invalid sort order passed to QueryExecutor::setSortOrder():" << order; + break; + } +} + +Qt::SortOrder QueryExecutor::Sort::getQtOrder() const +{ + // The column should be checked first for being > -1. + if (order == QueryExecutor::Sort::DESC) + return Qt::DescendingOrder; + + return Qt::AscendingOrder; +} + +QueryExecutor::SortList QueryExecutor::getSortOrder() const +{ + return sortOrder; +} + +void QueryExecutor::setSortOrder(const SortList& value) +{ + sortOrder = value; +} + +int operator==(const QueryExecutor::SourceTable& t1, const QueryExecutor::SourceTable& t2) +{ + return t1.database == t2.database && t1.table == t2.table && t1.alias == t2.alias; +} + +int qHash(QueryExecutor::SourceTable sourceTable) +{ + return qHash(sourceTable.database + "." + sourceTable.table + "/" + sourceTable.alias); +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h new file mode 100644 index 0000000..c4a3e4d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h @@ -0,0 +1,1369 @@ +#ifndef QUERYEXECUTOR_H +#define QUERYEXECUTOR_H + +#include "db/db.h" +#include "parser/token.h" +#include "selectresolver.h" +#include "coreSQLiteStudio_global.h" +#include "common/bistrhash.h" +#include "datatype.h" +#include +#include +#include +#include + +/** @file */ + +class Parser; +class SqliteQuery; +class QueryExecutorStep; +class DbPlugin; + +/** + * @brief Advanced SQL query execution handler. + * + * QueryExecutor is an advanced SQL query execution handler, which lets you execute any query (with subqueries, joins, etc) + * and is capable of providing meta information about returned data, such as ROWID for all rows and columns, + * data sources (database, table and column) for every column, rows affected, total rows number for query, etc. + * All of this available for both SQLite versions: 2 and 3. + * + * Queries are executed asynchronously. To handle result a lambda function can be used (or any function pointer), + * or manual tracking of asynchronous execution ID and signals from this class. Function pointers and lambdas + * are recommended way to handle results. + * + * It also allows you to: + *
    + *
  • programatically define sorting on desired column.
  • + *
  • define result rows paging (page size and queried page)
  • + *
  • refer other databases by their symbolic name and they will be attached and detached on the fly
  • + *
  • define maximum cell data size (in bytes), so you won't read too much data at once
  • + *
+ * + * Total number of result rows is counted by a separate call to the database (using SELECT count(*) ...) + * and its result is provided later, which is signalized by signal resultsCountingFinished(). Row counting can + * be disabled with setSkipRowCounting(). See "Counting query" section below for details. + * + * The simplest use case would be: + * @code + * Db* db = getDb(); + * QueryExecutor *executor = new QueryExecutor(db, "SELECT * FROM table"); + * executor->exec([=](SqlQueryPtr results) + * { + * if (results->isError()) + * { + * qCritical() << "Error " << results->getErrorCode() << ": " << results->getErrorText() << "\n"; + * return; + * } + * qDebug() << results->valueList(); + * } + * @endcode + * + * Unless you want some of QueryExecutor's special features, it's recommended to use + * Db::exec() and Db::asyncExec(), because while QueryExecutor is powerful, it also does lots of thing underneeth + * you may not need at all. + * + * \note This class is used in SQL editor window (SqlQueryModel) to execute queries entered by the user. + * + * \section smart_simple_sec "smart mode" vs "simple mode" + * + * Documentation of this class references many times to "smart mode" and "simple mode" expressions. + * The "smart mode" means that the QueryExecutor was able to parse the input query, modify it for its needs + * (add some meta-information columns, etc) and executed modified query successfully. + * When the "smart mode" fails (which should be rare), the "simple mode" steps in as a fallback strategy. + * The "simple mode" doesn't modify input query, just directly executes in on the database + * and then QueryExecutor tries to extract as much meta-information from "simple mode" as it can (which is not much). + * + * The "simple mode" also doesn't apply any paging (see QueryExecutor::setPage()), nor data size limits + * (see QueryExecutor::setDataLengthLimit()). + * + * The meta-information is all the data from the query that is not the essential data requested in the input query. + * That is full description on all requested columns (their source tables, databases, data types), + * ROWID value for all returned data rows, and more... + * + * \section counting_query Counting query + * + * QueryExecutor can split results into pages. In such cases, results are not all read, instead they are limited + * at SQL level with LIMIT and OFFSET keywords. Because of that it is impossible to tell how many rows + * would actualy be returned if there were no limit keywords. + * + * To deal with it the QueryExecutor makes extra query execution, which happens asynchronously to the main query + * execution. This extra execution starts just after the main query execution has finished (with success). + * This extra query (aka "Counting query") is made of original query wrapped with: + * @code + * SELECT count(*) FROM (original_query) + * @endcode + * This way QueryExecutor know the true number of rows to be retuend by the query. + * + * Since this extra query execution takes some extra time, this is performed asynchronously and only after + * successful execution of the main query. If you need to work with QueryExecutor::getTotalRowsReturned(), + * wait for the QueryExecutor::resultsCountingFinished() signal first. + * + * Row counting query execution can be disabled with QueryExecutor::setSkipRowCounting(), + */ +class API_EXPORT QueryExecutor : public QObject, public QRunnable +{ + Q_OBJECT + + public: + /** + * @brief General reasons for which results data cannot be edited. + */ + enum class EditionForbiddenReason + { + NOT_A_SELECT, /**< Executed query was not a SELECT. Only SELECT results can be edited. */ + SMART_EXECUTION_FAILED /**< + * QueryExecutor could not perform "smart" execution, + * which means that it was unable to gather meta information + * about returned data and therefore it cannot tell what are ROWIDs + * or data sources for each column. Still it was able to perform + * simple (direct, without query modifications) execution + * and it returned results, so they can be presented to the user, + * but not edited. + * + * This happens usually when there's a but in SQLiteStudio, + * which caused - for example - error during query parsing by Parser, + * or other query syntax issues, that wasn't handled correctly + * by SQLiteStudio. + */ + }; + + /** + * @brief Per-column reasons for which the data in the column cannot be edited. + */ + enum class ColumnEditionForbiddenReason + { + COMPOUND_SELECT, /**< + * The data cell comes from compound SELECT (UNION, EXCEPT, INTERSECT), + * which makes it problematic to SQLiteStudio to find out to which table + * does the particular row belong to. + * + * It might be resolved in future SQLiteStudio versions and this enum value + * would disappear then. + */ + GROUPED_RESULTS, /**< + * The data cell comes from SELECT with aggregated results, therefore it's + * hard to hard what were ROWIDs of each row in the results. + * + * It might be resolved in future SQLiteStudio versions and this enum value + * would disappear then. + */ + DISTINCT_RESULTS, /**< + * The data cell comes from SELECT DISTINCT clause, therefore extracting + * ROWIDs from the results is impossible, becasuse querying ROWID would + * make every row unique, therefore DISTINCT would not remove any rows, + * even the rest of the data (which matters to the user) would not be + * unique and should have been removed by the DISTINCT keyword. + * + * Because of that, SQLiteStudio doesn't extract ROWIDs for DISTINCT + * queries, so the results are accurate, but in consequence, + * the data cannot be edited. + */ + EXPRESSION, /**< + * The data cell is a result of a formula, function or other expression, + * which is not a direct data source, therefore it's impossible to change + * it's value. + */ + SYSTEM_TABLE, /**< + * The data cell comes from system table (sqlite_*) and those tables cannot + * be edited. + */ + COMM_TAB_EXPR, /**< + * The data cell comes from system "WITH common-table-expression" SELECT + * statement and those tables cannot be edited for the same reasons as + * in COMPOUND_SELECT case. To learn about common table expression statement, + * see http://sqlite.org/lang_with.html + */ + }; + + /** + * @brief Sort order definition. + * + * QueryExecutor supports programmatic sort order definition. + * It supports smooth transition from/to Qt sorting direction enum + * and defines sorting column by its index (0-based). + */ + struct API_EXPORT Sort + { + /** + * @brief Sorting order. + */ + enum Order + { + ASC, /**< Ascending order */ + DESC, /**< Descending order */ + NONE /**< No sorting at all */ + }; + + /** + * @brief Default constructor with no sorting defined. + * + * Constructed object uses NONE as sorting order. + */ + Sort(); + + /** + * @brief Creates sort order with given order on given column. + * @param order Order to sort with. + * @param column 0-based column number. + */ + Sort(Order order, int column); + + /** + * @brief Creates sort order with given order on given column. + * @param order Qt typed sort order (Qt::AscendingOrder, Qt::DescendingOrder). + * @param column 0-based column number. + */ + Sort(Qt::SortOrder order, int column); + + /** + * @brief Gets Qt typed sort order. + * @return Sort order. + */ + Qt::SortOrder getQtOrder() const; + + /** + * @brief Sorting order. + */ + Order order = NONE; + + /** + * @brief 0-based column number to sort by. + */ + int column = -1; + }; + + typedef QList SortList; + + /** + * @brief ResultColumn as represented by QueryExecutor. + * + * QueryExecutor has its own result column representation, because it provides more + * meta information on the column. + */ + struct API_EXPORT ResultColumn + { + /** + * @brief Database name that the result column comes from. + * + * It's an SQLite internal name of the database, which means it's either "main", or "temp", + * or symbolic name of registered database (as represented in the databases tree), + * or the name of any attached databases. + * + * Symbolic database name is provided when user used it in his query and SQLiteStudio attached + * it transparently. In that case the temporary name used for "ATTACH" statement would make no sense, + * because that database was detached automatically after the query execution finished. + * + * In case of databases attached manually by user, it's exactly the same string as used when executing + * "ATTACH" statement. + */ + QString database; + + /** + * @brief Table name that the result column comes from. + */ + QString table; + + /** + * @brief Table column name that the result column comes from. + */ + QString column; + + /** + * @brief Alias defined for the result column in the query. + */ + QString alias; + + /** + * @brief Table alias defined in the query. + * + * This is an alias defined in the query for the table that the result column comes from. + */ + QString tableAlias; + + /** + * @brief Name of the column as presented to user. + * + * This is the name of a column as SQLite would present it to the user. + * If the query requested just a column from table, it will be that column name. + * If the query resuested two columns with the same name, then the second column will get + * suffix ":1", next one would get suffix ":2", and so on. + * For expressions the display name is direct copy of the SQL code used to define the expression. + * + * If the alias was defined in query, than it's used for the display name instead of anything else. + */ + QString displayName; + + /** + * @brief QueryExecutor's internal alias for the column. + * + * This value has no sense outside of QueryExecutor. It's used by QueryExecutor to + * keep track of columns from subselects, etc. + */ + QString queryExecutorAlias; + + /** + * @brief Set of reasons for which column editing is denied. + * + * If the set is empty, it means that the column can be edited. + */ + QSet editionForbiddenReasons; + + /** + * @brief Flag indicating that the column is actually an expression. + * + * Column representing an expression is not just a column and it should not be ever wrapped with + * quoting wrapper ([], "", ``). Such a column is for example call to the SQL function. + * + * For regular columns this will be false. + */ + bool expression = false; + }; + + /** + * @brief Shared pointer to ResultColumn. + */ + typedef QSharedPointer ResultColumnPtr; + + /** + * @brief Combined row ID columns for tables in the query. + * + * Since version 3.8.2 SQLite introduced the "WITHOUT ROWID" clause. It allows tables to have no + * ROWID built-in. Such tables must have PRIMARY KEY defined, which does the job of the unique key + * for the table. + * + * This structure describes the unique key for the table, regardless if it's a regular ROWID, + * or if it's a PRIMARY KEY on a column, or if it's a multi-column PRIMARY KEY. + * + * You should always understand it as a set of PRIMARY KEY columns for given table. + * Referencing to that table using given columns will guarantee uniqueness of the row. + * + * In case of regular table (with no "WITHOUT ROWID" clause), there will be only one column + * defined in ResultRowIdColumn::columns and it will be named "ROWID". + */ + struct API_EXPORT ResultRowIdColumn + { + /** + * @brief Database name that the table with this row ID is in. + */ + QString database; + + /** + * @brief Table name that the row ID is for. + */ + QString table; + + /** + * @brief Table alias defined in the query. + * @see ResultColumn::tableAlias + */ + QString tableAlias; + + /** + * @brief Mapping from alias to real column. + * + * This is mapping from QueryExecutor's internal aliases for columns + * into primary key column names of the table that the result column comes from. + * + * If you want to get list of column names used for RowId, use values() on this member. + * If you want to get list of query executor aliases, use keys() on this member. + */ + QHash queryExecutorAliasToColumn; + }; + + /** + * @brief Shared pointer to ResultRowIdColumn. + */ + typedef QSharedPointer ResultRowIdColumnPtr; + + /** + * @brief Table that was a data source for at least one column in the query. + */ + struct API_EXPORT SourceTable + { + /** + * @brief Table's database. + * + * Same rules apply as for ResultColumn::database. + */ + QString database; + + /** + * @brief Table name. + */ + QString table; + + /** + * @brief Table alias defined in query. + */ + QString alias; + }; + + /** + * @brief Shared pointer to SourceTable. + */ + typedef QSharedPointer SourceTablePtr; + + /** + * @brief Query execution context. + * + * This class is used to share data across all executor steps. + * It also provides initial configuration for executor steps. + */ + struct Context + { + /** + * @brief Query string after last query step processing. + * + * Before any step was executed, this is the same as originalQuery. + * + * The processed query is the one that will be executed in the end, + * so any steps should apply their changes to this query. + * + * This string should be modified and updated from QueryExecutorStep implementations. + * + * You won't usually modify this string directly. Instead you will + * want to use one of 2 methods: + *
    + *
  • Modify tokens
  • - modify tokens of top level objects in parsedQueries + * and call QueryExecutorStep::updateQueries(). + *
  • Modify parsed objects
  • - modify logical structure and values of + * objects in parsedQueries, then call on those objects SqliteStatement::rebuildTokens() + * and finally call QueryExecutorStep::updateQueries. + *
+ * + * The parsedQueries are refreshed every time when QueryExecutor executes + * QueryExecutorParse step. + */ + QString processedQuery; + + /** + * @brief Number of milliseconds that query execution took. + * + * This is measured and set by QueryExecutorStepExecute step. + */ + qint64 executionTime = 0; + + /** + * @brief Number of rows affected by the query. + */ + qint64 rowsAffected = 0; + + /** + * @brief Total number of rows returned from query. + * + * It provides correct number for all queries, no matter if it's SELECT, PRAGMA, or other. + */ + qint64 totalRowsReturned = 0; + + /** + * @brief Total number of pages. + * + * If there's a lot of result rows, they are split to pages. + * There's always at least one page of results. + */ + int totalPages = 1; + + /** + * @brief Defines if row counting will be performed. + * + * In case of EXPLAIN or PRAGMA queries the number of result rows is not provided from + * SQLite (at least not from Qt's drivers for them). Instead we need to manually count + * number of rows. This is when this flag is set (it's done by QueryExecutor, + * no need to care about it). + */ + bool rowsCountingRequired = false; + + /** + * @brief Executing query in EXPLAIN mode. + * + * This is configuration parameter passed from QueryExecutor just before executing + * the query. It can be defined by QueryExecutor::setExplainMode(). + */ + bool explainMode = false; + + /** + * @brief Defines if row counting should be skipped. + * + * This is a configuration flag predefined by QueryExecutor just before executing starts. + * You can set it with QueryExecutor::setSkipRowCounting(). + * + * Row counting is done asynchronously, just after normal query execution is finished. + * It's done by executing yet another query, which is more or less an orginal query + * wrapped with "SELECT count(*) FROM (...)". + * + * Separate counting has to be done, because QueryExecutor adds LIMIT and OFFSET + * to SELECT queries for results paging. + * + * When counting is done, the resultsCountingFinished() signal is emitted. + */ + bool skipRowCounting = false; + + /** + * @brief Parameters for query execution. + * + * It's defined by setParam(). + */ + QHash queryParameters; + + /** + * @brief Results handler function pointer. + * + * This serves the same purpose as in Db class. It's used for execution + * with results handled by provided function. See Db::QueryResultsHandler for details. + * + * It's defined by exec(). + */ + Db::QueryResultsHandler resultsHandler = nullptr; + + /** + * @brief List of queries parsed from input query string. + * + * List of parsed queries is updated each time the QueryExecutorParseQuery step + * is executed. When it's called is defined by QueryExecutor::executionChain. + */ + QList parsedQueries; + + /** + * @brief Results of executed query. + * + * This is results object defined by the final query execution. It means that the + * query executed passed all preprocessing steps and was executed in its final form. + * + * This member is defined by QueryExecutorExecute step. + */ + SqlQueryPtr executionResults; + + /** + * @brief Currently attached databases. + * + * This is a cross-context information about currently attached databases. + * As QueryExecutorAttaches step does attaching, other steps may need information + * about attached databases. It's a map of orginal_db_name_used to attached_name. + */ + BiStrHash dbNameToAttach; + + /** + * @brief Sequence used by executor steps to generate column names. + */ + int colNameSeq = 0; + + /** + * @brief List of reasons that editing results is forbidden for. + * + * Executor steps may decide that the results of query cannot be edited. + * In that case they add proper enum to this set. + */ + QSet editionForbiddenReasons; + + /** + * @brief Result columns that provide ROWID. + * + * QueryExecutorAddRowIds step adds those columns. There is one or more columns + * per data source table mentioned in the query. It depends on "WITHOUT ROWID" clause + * in CREATE TABLE of the source table. + */ + QList rowIdColumns; + + /** + * @brief Result columns from the query. + * + * List of result columns, just like they would be returned from regular execution + * of the query. Column in this list are not just a names of those columns, + * they provide full meta information about every single column. + */ + QList resultColumns; + + /** + * @brief Data source tables mentioned in the query. + * + * List of tables used as data source in the query. + */ + QSet sourceTables; + + /** + * @brief Query used for counting results. + * + * Filled with SQL to be used for results counting (even if counting is disabled). + * @see QueryExecutor::getCountingQuery() + */ + QString countingQuery; + + /** + * @brief Flag indicating results preloading. + * + * Causes flag Db::Flag::PRELOAD to be added to the query execution. + */ + bool preloadResults = false; + + /** + * @brief Tells if executed queries did modify database schema. + * + * This is defined by QueryExecutorDetectSchemaAlter step + * and can be accessed by QueryExecutor::wasSchemaModified(). + */ + bool schemaModified = false; + + /** + * @brief Forbids QueryExecutor to return meta columns. + * + * See QueryExecutor::noMetaColumns for details. + */ + bool noMetaColumns = false; + }; + + /** + * @brief Creates query executor, initializes internal context object. + * @param db Optional database. If not provided, it has to be defined later with setDb(). + * @param query Optional query to execute. If not provided, it has to be defined later with setQuery(). + * @param parent Parent QObject. + */ + QueryExecutor(Db* db = nullptr, const QString& query = QString::null, QObject *parent = 0); + + /** + * @brief Releases internal resources. + */ + ~QueryExecutor(); + + /** + * @brief Defined query to be executed. + * @param query SQL query string. + * + * The query string can actually be multiple queries separated with a semicolon, just like you would + * write multiple queries in the SQL Editor window. Query executor will handle that. + * + * The query can contain parameter placeholders (such as :param, \@param). To bind values to params + * use setParam(). + */ + void setQuery(const QString& query); + + /** + * @brief Executes the query. + * @param resultsHandler Optional handler function pointer, can be lambda function. See Db::QueryResultsHandler for details. + * + * While execution is asynchronous, the executor notifies about results by signals. + * In case of success emits executionFinished(), in case of error emits executionFailed(). + */ + void exec(Db::QueryResultsHandler resultsHandler = nullptr); + + /** + * @brief Interrupts current execution. + * + * Calls Db::asyncInterrupt() internally. + */ + void interrupt(); + + /** + * @brief Executes counting query. + * + * Executes (asynchronously) counting query for currently defined query. After execution is done, the resultsCountingFinished() + * signal is emitted. + * + * Counting query is made of original query wrapped with "SELECT count(*) FROM (original_query)". + * + * It is executed after the main query execution has finished. + */ + void countResults(); + + /** + * @brief Gets time of how long it took to execute query. + * @return Execution time in milliseconds. + * + * The execution time is number of milliseconds from begining of the query execution, till receiving of the results. + */ + qint64 getLastExecutionTime() const; + + /** + * @brief Gets number of rows affected by the query. + * @return Affected rows number. + * + * Rows affected are defined by DbPlugin implementation and are usually a number of rows modified by UPDATE statement, + * or deleted by DELETE statement, or inserted by INSERT statement. + */ + qint64 getRowsAffected() const; + + /** + * @brief Gets number of rows returned by the query. + * @return Number of rows. + * + * If QueryExecutor limits result rows number (if defined by setResultsPerPage()), the actual number of rows + * to be returned from query can be larger. This methods returns this true number of rows, + * that would be returned from the query. + * + * Calling this method makes sense only after resultsCountingFinished() was emitted, otherwise the value + * returned will not be accurate. + */ + qint64 getTotalRowsReturned() const; + + /** + * @brief Gets type of the SQL statement in the defined query. + * @param index Index of the SQL statement in the query (statements are separated by semicolon character), or -1 to get the last one. + * @return Type of the query. If there were no parsed queries in the context, or if passed index is out of range, + * then SqliteQueryType::UNDEFINED is returned. + */ + SqliteQueryType getExecutedQueryType(int index = -1); + + /** + * @brief Provides set of data source tables used in query. + * @return Set of tables. + */ + QSet getSourceTables() const; + + /** + * @brief Gets number of pages available. + * @return Number of pages. + * + * Since QueryExecutor organizes results of the query into pages, this method gives number of pages that is necessary + * to display all the data. In other words: "results of this method" - 1 = "last page index". + * + * Single page size is defined by setResultsPerPage(). + */ + int getTotalPages() const; + + /** + * @brief Gets ordered list of result columns. + * @return Result columns. + * + * See Context::resultColumns for details. + */ + QList getResultColumns() const; + + /** + * @brief Gets list of ROWID columns. + * @return ROWID columns. + * + * Note, that this returns list of ROWID columns as entities. This means that for ROWID a single ROWID column + * can be actually couple of columns in the results. To count the ROWID columns offset for extracting + * data columns use getMetaColumnCount(). + * + * See Context::rowIdColumns for details. + */ + QList getRowIdResultColumns() const; + + /** + * @brief Gives number of meta columns in the executed query. + * @return Number of the actual meta columns (such as ROWID columns) added to the executed query. + * + * This method should be used to find out the number of meta columns that were added to the begining + * of the result columns in the executed query. This way you can learn which column index use as a start + * for reading the actual data from the query. + * + * Meta columns are used by QueryExecutor to find more information about the query being executed + * (like ROWID of each row for example). + */ + int getMetaColumnCount() const; + + /** + * @brief Gets reasons for which editing results is forbidden. + * @return Set of reasons. + * + * See Context::editionForbiddenReasons for details. + */ + QSet getEditionForbiddenGlobalReasons() const; + + /** + * @brief Defines named bind parameter for the query. + * @param name Name of the parameter (without the : or @ prefix). + * @param value Value of the parameter. + * + * Positional (index oriented) parameters are not supported by the QueryExecutor. + * Always use named parameters with QueryExecutor. + */ + void setParam(const QString& name, const QVariant& value); + + /** + * @brief Replaces placeholder in the query. + * @param value Value to replace placeholder with. + * + * This works almost the same as QString::arg(), but it's specialized + * for SQL domain. It means that it will work only with numeric + * or string values passed in the parameter. If the value is numeric, + * then it just replaces a placeholder. If the value is a string, + * then it's wrapped with a quote character ('), if necessary, then + * it replaces a placeholder. + * + * Placeholders are the same as for QString::arg(): %1, %2, %3... + */ + void arg(const QVariant& value); + + /** + * @brief Gets currently defined database. + * @return Database object, or null pointer if not yet defined. + */ + Db* getDb() const; + + /** + * @brief Defines new database for query execution. + * @param value Database object. It should be open before calling exec(). + */ + void setDb(Db* value); + + /** + * @brief Gets original, not modified query. + * @return SQL query string. + */ + QString getOriginalQuery() const; + + /** + * @brief Gets data size limit. + * @return Number of bytes, or UTF-8/UTF-16 characters. + * + * See setDataLengthLimit() for details. + */ + int getDataLengthLimit() const; + + /** + * @brief Defines data size limit for results. + * @param value Number of bytes, or UTF-8/UTF-16 characters. + * + * Limit is not defined by default and in that case it's not applied + * to the query. To enable limit, set it to any positive number. + * To disable limit, set it to any negative number. + * + * When QueryExecutor prepares query for execution, it applies SUBSTR() + * to all result columns, so if the database has a huge value in some column, + * SQLiteStudio won't load 1000 rows with huge values - that would kill performance + * of the application. Instead it loads small chunk of every value. + * + * SqlQueryModel loads limited chunks of data and loads on-the-fly full cell values + * when user requests it (edits the cell, or views it in form editor). + * + * Parameter defined by this method is passed to SQLite's SUBSTR() function. + * As SQLite's documentation stand, numbers passed to that function are treated + * as number of bytes for non-textual data and for textual data they are number + * of characters (for UTF-8 and UTF-16 they can be made of more than 1 byte). + */ + void setDataLengthLimit(int value); + + // TODO manual row counting -> should be done by query executor already and returned in total rows + /** + * @brief Tests if manual row counting is required. + * @return True if manual counting is required. + * + * In case of some queries the getTotalRowsReturned() won't provide proper value. + * Then you will need to count result rows from the results object. + * + * It's okay, because this applies only for EXPLAIN and PRAGMA queries, + * which will never return any huge row counts. + */ + bool isRowCountingRequired() const; + + /** + * @brief Gets SQL query used for counting results. + * @return SQL query. + * + * This is the query used by countResults(). + */ + QString getCountingQuery() const; + + /** + * @brief Gets number of rows per page. + * @return Number of rows. + * + * By default results are not split to pages and this method will return -1. + */ + int getResultsPerPage() const; + + /** + * @brief Defines number of rows per page. + * @param value Number of rows. + * + * By default results are not split to pages. + * See setPage() for details on enabling and disabling paging. + */ + void setResultsPerPage(int value); + + /** + * @brief Gets current results page. + * @return Page index. + * + * Results page is 0-based index. It's always value between 0 and (getTotalPages() - 1). + * If results paging is disabled (see setResultsPerPage()), then this method + * will always return 0, as this is the first page (and in that case - the only one). + */ + int getPage() const; + + /** + * @brief Defines results page for next execution. + * @param value 0-based page index. + * + * If page value is negative, then paging is disabled. + * Any positive value or 0 enables paging and sets requested page of results to given page. + * + * If requested page value is greater than "getTotalPages() - 1", then no results will be returned. + * It's an invalid page value. + * If requested page value is lower then 0, then paging is disabled. + * + * Once the page is defined, the exec() must be called to get results + * from new defined page. + */ + void setPage(int value); + + /** + * @brief Tests if there's any execution in progress at the moment. + * @return true if the execution is in progress, or false otherwise. + */ + bool isExecutionInProgress(); + + /** + * @brief Gets sorting defined for executor. + * @return Sorting definition. + * + * See Sort for details. + */ + QueryExecutor::SortList getSortOrder() const; + + /** + * @brief Defines sorting for next query execution. + * @param value Sorting definition. + * + * Once the sorting definition is changed, the exec() must be called + * to receive results in new order. + */ + void setSortOrder(const QueryExecutor::SortList& value); + + /** + * @brief Tests if row counting is disabled. + * @return true if row counting will be skipped, or false otherwise. + * + * See Context::skipRowCounting for details. + */ + bool getSkipRowCounting() const; + + /** + * @brief Defines if executor should skip row counting. + * @param value New value for this parameter. + * + * See Context::skipRowCounting for details. + */ + void setSkipRowCounting(bool value); + + /** + * @brief Asynchronous executor processing in thread. + * + * This is an implementation of QRunnable::run(), so the QueryExecutor + * does it's own asynchronous work on object members. + */ + void run(); + + /** + * @brief Tests if query execution should be performed in EXPLAIN mode. + * @return true if the mode is enabled, or false otherwise. + */ + bool getExplainMode() const; + + /** + * @brief Defines EXPLAIN mode for next query execution. + * @param value true to enable EXPLAIN mode, or false to disable it. + * + * EXPLAIN mode means simply that the EXPLAIN keyword will be prepended + * to the query, except when the query already started with the EXPLAIN keyword. + * + * Once the mode is changed, the exec() must be called + * to receive "explain" results. + */ + void setExplainMode(bool value); + + /** + * @brief Defines results preloading. + * @param value true to preload results. + * + * Results preloading is disabled by default. See Db::Flag::PRELOAD for details. + */ + void setPreloadResults(bool value); + + bool getAsyncMode() const; + void setAsyncMode(bool value); + + SqlQueryPtr getResults() const; + bool wasSchemaModified() const; + + static QList resolveColumnTypes(Db* db, QList& columns, bool noDbLocking = false); + + bool getNoMetaColumns() const; + void setNoMetaColumns(bool value); + + private: + /** + * @brief Executes query. + * + * It's called from run(). This is the execution of query but called from different + * thread than exec() was called from. + */ + void execInternal(); + + /** + * @brief Raises execution error. + * @param code Error code. Can be either from SQLite error codes, or from SqlErrorCode. + * @param text Error message. + * + * This method is called when some of executor's preconditions has failed, or when SQLite + * execution raised an error. + */ + void error(int code, const QString& text); + + /** + * @brief Build chain of executor steps. + * + * Defines executionChain by adding new QueryExecutorStep descendants. + * Each step has its own purpose described in its class documentation. + * See inheritance hierarchy of QueryExecutorStep. + */ + void setupExecutionChain(); + + /** + * @brief Deletes executor step objects. + * + * Deletes all QueryExecutorStep objects from executionChain clears the list. + */ + void clearChain(); + + /** + * @brief Executes all steps from executor chain. + * + * The steps chain is defined by setupExecutionChain(). + * On execution error, the stepFailed() is called and the method returns. + */ + void executeChain(); + + /** + * @brief Executes the original, unmodified query. + * + * When smart execution (using steps chain) failed, then this method + * is a fallback. It executes original query passed to the executor. + * Given, that query was not modified, it cannot provide meta information, + * therefore results of that execution won't editable. + */ + void executeSimpleMethod(); + + /** + * @brief Handles results of simple execution. + * @param results Results object returned from Db. + * + * Checks results for errors and extracts basic meta information, + * such as rows affected, total result rows and time of execution. + * + * In case of success emits executionFinished(), in case of error emits executionFailed(). + */ + void simpleExecutionFinished(SqlQueryPtr results); + + /** + * @brief Tests whether the original query is a SELECT statement. + * @return true if the query is SELECT, or false otherwise. + * + * This method assumes that there was a problem with parsing the query with the Parser + * (and that's why we're using simple execution method) and so it tries to figure out + * a query type using other algorithms. + */ + bool simpleExecIsSelect(); + + /** + * @brief Releases resources acquired during query execution. + * + * Currently it just detaches databases attached for query execution needs (transparent + * database attaching feature). + */ + void cleanup(); + + /** + * @brief Extracts counting query results. + * @param asyncId Asynchronous ID of the counting query execution. + * @param results Results from the counting query execution. + * @return true if passed asyncId is the one for currently running counting query, or false otherwise. + * + * It's called from database asynchronous execution thread. The database might have executed + * some other acynchronous queries too, so this method checks if the asyncId is the expected one. + * + * Basicly this method is called a result of countResults() call. Extracts counted number of rows + * and stores it in query executor's context. + */ + bool handleRowCountingResults(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief Query executor context object. + * + * Context object is shared across all execution steps. It's (re)initialized for every + * call to exec(). Initialization involves copying configuration parameters (such as sortOrder, + * explainMode, etc) from local members to the context. + * + * During steps execution the context is used to share information between steps. + * For example if one step modifies query in anyway, it should store updated query + * in Context::processedQuery. See QueryExecutorStep for details on possible methods + * for updating Context::processedQuery (you don't always have to build the whole processed + * query string by yourself). + * + * Finally, the context serves as a results container from all steps. QueryExecutor reads + * result columns metadata, total rows number, affected rows and other information from the context. + */ + Context* context = nullptr; + + /** + * @brief Database that all queries will be executed on. + * + * It can be passed in constructor or defined later with setDb(), but it cannot be null + * when calling exec(). The exec() will simply return with no execution performed + * and will log a warning. + */ + Db* db = nullptr; + + /** + * @brief Synchronization mutex for "execution in progress" state of executor. + * + * The state of "execution in progress" is the only value synchronized between threads. + * It makes sure that single QueryExecutor will execute only one query at the time. + */ + QMutex executionMutex; + + /** + * @brief Query to execute as defined by the user. + * + * This is a copy of original query provided by user to the executor. + */ + QString originalQuery; + + /** + * @brief Predefined number of results per page. + * + * See setResultsPerPage() for details. + */ + int resultsPerPage = -1; + + /** + * @brief Predefined results page index. + * + * See setPage() for details. + */ + int page = -1; + + /** + * @brief Predefined sorting order. + * + * There's no sorting predefined by default. If you want it, you have to apply it with setSortOrder(). + */ + SortList sortOrder; + + /** + * @brief Flag indicating that the execution is currently in progress. + * + * This variable is synchronized across threads and therefore you can always ask QueryExecutor + * if it's currently busy (with isExecutionInProgress()). + */ + bool executionInProgress = false; + + /** + * @brief Flag indicating that the most recent execution was made in "simple mode". + * + * This flag is set by executeSimpleMethod() method. See its documentation for details. + * The exec() resets this flag to false each time, but each time the smart execution fails, + * the executeSimpleMethod() is called and the flag is set to true. + */ + bool simpleExecution = false; + + /** + * @brief Flag indicating that the most recent execution was interrupted. + * + * This flag is set only if execution was interrupted by call to interrupt() on this class. + * If the execution was interrupted by another thread (which called sqlite_interrupt() + * or Db::interrupt()), then this flag is not set. + * + * This variable is tested at several stages of query execution in order to abort + * execution if the interruption was already requested. + */ + bool interrupted = false; + + /** + * @brief Flag indicating that the execution is performed in EXPLAIN mode. + * + * See setExplainMode() for details. + */ + bool explainMode = false; + + /** + * @brief Flag indicating that the row counting was disabled. + * + * See Context::skipRowCounting for details. + */ + bool skipRowCounting = false; + + /** + * @brief Defines results data size limit. + * + * See setDataLengthLimit() for details. + */ + int dataLengthLimit = -1; + + /** + * @brief Exact moment when query execution started. + * + * Expressed in number of milliseconds since 1970-01-01 00:00:00. + */ + qint64 simpleExecutionStartTime; + + /** + * @brief Asynchronous ID of query execution. + * + * Asynchronous ID returned from Db::asyncExec() for the query execution. + */ + quint32 asyncId = 0; + + /** + * @brief Asynchronous ID of counting query execution. + * + * Asynchronous ID returned from Db::asyncExec() for the counting query execution. + * See countResults() for details on counting query. + */ + quint32 resultsCountingAsyncId = 0; + + /** + * @brief Flag indicating results preloading. + * + * See Context::preloadResults. + */ + bool preloadResults = false; + + /** + * @brief Determinates if asynchronous mode is used. + * + * By default QueryExecutor runs in asynchronous mode (in another thread). + * You can set this to false to make exec() work synchronously, on calling thread. + */ + bool asyncMode = true; + + /** + * @brief Defines if the QueryExecutor will provide meta columns in the results. + * + * Set to true to forbid providing meta columns, or leave as false to let QueryExecutor + * provide meta columns. + * + * Meta columns are additional columns that are not part of the query that was passed to the executor. + * Those are for example ROWID columns (currently those are the only meta columns). + * + * You can always find out number of ROWID columns from getRowIdResultColumns(). + * + * Meta columns are placed always at the begining. + */ + bool noMetaColumns = false; + + /** + * @brief Chain of executor steps. + * + * Executor step list is set up by setupExecutionChain() and cleaned up after + * execution is finished. Steps are executed in order they appear in this list. + * + * Steps are executed one by one and if any of them raises the error, + * execution stops and error from QueryExecutor is raised (with executionFailed() signal). + */ + QList executionChain; + + /** + * @brief Execution results handler. + * + * This member keeps address of function that was defined for handling results. + * It is defined only if exec() method was called with the handler function argument. + * + * Results handler function is evaluated once the query execution has finished + * with success. It's not called on failure. + */ + Db::QueryResultsHandler resultsHandler = nullptr; + + signals: + /** + * @brief Emitted on successful query execution. + * @param results Results from query execution. + * + * It's emitted at the very end of the whole query execution process + * and only on successful execution. It doesn't matter if the execution was + * performed in "smart mode" or "simple mode". + */ + void executionFinished(SqlQueryPtr results); + + /** + * @brief Emitted on failed query execution. + * @param code Error code. + * @param errorMessage Error message. + * + * It doesn't matter if the execution was performed in "smart mode" or "simple mode". + */ + void executionFailed(int code, QString errorMessage); + + /** + * @brief Emitted on successful counting query execution. + * @param rowsAffected Rows affected by the original query. + * @param rowsReturned Rows returned by the original query. + * @param totalPages Number of pages needed to represent all rows given the value defined with setResultsPerPage(). + * + * This signal is emitted only when setSkipRowCounting() was set to false (it is by default) + * and the counting query execution was successful. + * + * The counting query actually counts only \p rowsReturned, while \p rowsAffected and \p totalPages + * are extracted from original query execution. + */ + void resultsCountingFinished(quint64 rowsAffected, quint64 rowsReturned, int totalPages); + + public slots: + /** + * @brief Executes given query. + * @param originalQuery to be executed. + * + * This is a shorthand for: + * @code + * queryExecutor->setQuery(query); + * queryExecutor->exec(); + * @endcode + * + * This exec() version is a SLOT, while the other exec() method is not. + */ + void exec(const QString& originalQuery); + + private slots: + /** + * @brief Handles asynchronous database execution results. + * @param asyncId Asynchronous ID of the execution. + * @param results Results from the execution. + * + * QueryExecutor checks whether the \p asyncId belongs to the counting query execution, + * or the simple execution. + * Dispatches query results to a proper handler method. + */ + void dbAsyncExecFinished(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief Calledn when an executor step has failed with its job. + * + * An executor step reported an error. "Smart execution" failed and now the executor will try + * to execute query with a "simple method". + */ + void stepFailed(QueryExecutorStep *currentStep); + + /** + * @brief Cleanup routines after successful query execution. + * @param results Query results. + * + * Releases resources that are no longer used. Currently simply calls cleanup(). + */ + void cleanupAfterExecFinished(SqlQueryPtr results); + + /** + * @brief Cleanup routines after failed query execution. + * @param code Error code. + * @param errorMessage Error message. + * + * Releases resources that are no longer used. Currently simply calls cleanup(). + */ + void cleanupAfterExecFailed(int code, QString errorMessage); + + /** + * @brief Called when the currently set db is about to be destroyed. + * + * Deletes results from the Context if there were any, so they are not referencing anything + * from deleted Db. Keeping results is dangerous, becuase the Db driver (plugin) is most likely to + * be unloaded soon and we won't be able to call results destructor. + */ + void cleanupBeforeDbDestroy(Db* dbToBeUnloaded, DbPlugin* plugin); +}; + +int qHash(QueryExecutor::EditionForbiddenReason reason); +int qHash(QueryExecutor::ColumnEditionForbiddenReason reason); +int qHash(QueryExecutor::SourceTable sourceTable); +int operator==(const QueryExecutor::SourceTable& t1, const QueryExecutor::SourceTable& t2); + +#endif // QUERYEXECUTOR_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp new file mode 100644 index 0000000..55203e4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp @@ -0,0 +1,216 @@ +#include "queryexecutoraddrowids.h" +#include "parser/ast/sqliteselect.h" +#include "selectresolver.h" +#include "common/utils_sql.h" +#include "parser/ast/sqlitecreatetable.h" +#include "schemaresolver.h" +#include + +bool QueryExecutorAddRowIds::exec() +{ + if (context->noMetaColumns) + return true; + + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + if (select->coreSelects.size() > 1) + return true; + + if (select->coreSelects.first()->distinctKw || select->coreSelects.first()->valuesMode) + return true; + + bool ok = true; + addRowIdForTables(select.data(), ok); + + if (!ok) + { + qCritical() << "Error in QueryExecutorAddRowIds step."; + return false; + } + + // ...and putting it into parsed query, then update processed query + select->rebuildTokens(); + updateQueries(); + + return true; +} + +QHash> QueryExecutorAddRowIds::addRowIdForTables(SqliteSelect* select, bool& ok, bool isTopSelect) +{ + QHash> rowIdColsMap; + if (select->coreSelects.size() > 1) + return rowIdColsMap; + + SqliteSelect::Core* core = select->coreSelects.first(); + + if (core->groupBy.size() > 0) + return rowIdColsMap; + + if (core->distinctKw) + return rowIdColsMap; + + // Go trough subselects to add ROWID result columns there and collect rowId mapping to use here. + foreach (SqliteSelect* subSelect, getSubSelects(core)) + { + rowIdColsMap.unite(addRowIdForTables(subSelect, ok, false)); + if (!ok) + return rowIdColsMap; + } + + // Getting all tables we need to get ROWID for + SelectResolver resolver(db, select->tokens.detokenize()); + resolver.resolveMultiCore = false; // multicore subselects result in not editable columns, skip them + + QSet tables = resolver.resolveTables(core); + foreach (const SelectResolver::Table& table, tables) + { + if (table.flags & (SelectResolver::FROM_COMPOUND_SELECT | SelectResolver::FROM_DISTINCT_SELECT | SelectResolver::FROM_GROUPED_SELECT)) + continue; // we don't get ROWID from compound, distinct or aggregated subselects + + if (!addResultColumns(core, table, rowIdColsMap, isTopSelect)) + { + ok = false; + return rowIdColsMap; + } + } + return rowIdColsMap; +} + +QList QueryExecutorAddRowIds::getSubSelects(SqliteSelect::Core* core) +{ + QList selects; + if (!core->from) + return selects; + + if (core->from->singleSource && core->from->singleSource->select) + selects << core->from->singleSource->select; + + foreach (SqliteSelect::Core::JoinSourceOther* otherSource, core->from->otherSources) + { + if (!otherSource->singleSource->select) + continue; + + selects << otherSource->singleSource->select; + } + + return selects; +} + +QHash QueryExecutorAddRowIds::getNextColNames(const SelectResolver::Table& table) +{ + QHash colNames; + + SchemaResolver resolver(db); + SqliteQueryPtr query = resolver.getParsedObject(table.database, table.table, SchemaResolver::TABLE); + SqliteCreateTablePtr createTable = query.dynamicCast(); + if (!createTable) + { + qCritical() << "No CREATE TABLE object after parsing and casting in QueryExecutorAddRowIds::getNextColNames(). Cannot provide ROWID columns."; + return colNames; + } + + if (createTable->withOutRowId.isNull()) + { + // It's a regular ROWID table + colNames[getNextColName()] = "ROWID"; + return colNames; + } + + SqliteStatement* primaryKey = createTable->getPrimaryKey(); + if (!primaryKey) + { + qCritical() << "WITHOUT ROWID table, but could not find // Co PRIMARY KEY in QueryExecutorAddRowIds::getNextColNames()."; + return colNames; + } + + SqliteCreateTable::Column::Constraint* columnConstr = dynamic_cast(primaryKey); + if (columnConstr) + { + colNames[getNextColName()] = dynamic_cast(columnConstr->parentStatement())->name; + return colNames; + } + + SqliteCreateTable::Constraint* tableConstr = dynamic_cast(primaryKey); + if (tableConstr) + { + foreach (SqliteIndexedColumn* idxCol, tableConstr->indexedColumns) + colNames[getNextColName()] = idxCol->name; + + return colNames; + } + + qCritical() << "PRIMARY KEY that is neither table or column constraint. Should never happen (QueryExecutorAddRowIds::getNextColNames())."; + return colNames; +} + +bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, + QHash>& rowIdColsMap, bool isTopSelect) +{ + QHash executorToRealColumns; + if (rowIdColsMap.contains(table)) + { + executorToRealColumns = rowIdColsMap[table]; // we already have resCol names from subselect + } + else + { + executorToRealColumns = getNextColNames(table); + rowIdColsMap[table] = executorToRealColumns; + } + + if (executorToRealColumns.size() == 0) + { + qCritical() << "No result column defined for a table in QueryExecutorAddRowIds::addResCols()."; + return false; + } + + QHashIterator it(executorToRealColumns); + while (it.hasNext()) + { + it.next(); + if (!addResultColumns(core, table, it.key(), it.value())) + return false; + } + + if (isTopSelect) + { + // Query executor result column description + QueryExecutor::ResultRowIdColumnPtr queryExecutorResCol = QueryExecutor::ResultRowIdColumnPtr::create(); + queryExecutorResCol->database = table.database; + queryExecutorResCol->table = table.table; + queryExecutorResCol->tableAlias = table.alias; + queryExecutorResCol->queryExecutorAliasToColumn = executorToRealColumns; + context->rowIdColumns << queryExecutorResCol; + } + + return true; +} + +bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, const QString& queryExecutorColumn, + const QString& realColumn) +{ + SqliteSelect::Core::ResultColumn* resCol = new SqliteSelect::Core::ResultColumn(); + resCol->setParent(core); + + resCol->expr = new SqliteExpr(); + resCol->expr->setParent(resCol); + + resCol->expr->initId(realColumn); + if (!table.alias.isNull()) + { + resCol->expr->table = table.alias; + } + else + { + if (!table.database.isNull()) + resCol->expr->database = table.database; + + resCol->expr->table = table.table; + } + resCol->asKw = true; + resCol->alias = queryExecutorColumn; + + core->resultColumns.insert(0, resCol); + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h new file mode 100644 index 0000000..a5431fa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h @@ -0,0 +1,81 @@ +#ifndef QUERYEXECUTORADDROWIDS_H +#define QUERYEXECUTORADDROWIDS_H + +#include "queryexecutorstep.h" +#include "parser/token.h" + +/** + * @brief Adds ROWID to result columns. + * + * This step adds ROWID to result column list for each table mentioned in result columns. + * For WITHOUT ROWID tables there might be several columns per table. + * + * It also provides list of added columns in QueryExecutor::Context::rowIdColumns. + */ +class QueryExecutorAddRowIds : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + /** + * @brief Adds ROWID columns to the result columns and the context. + * @param core SELECT's core that keeps result columns. + * @param table Table we want to add ROWID columns for. + * @param rowIdColsMap Map of ROWID columns from inner selects (built with addRowIdForTables()). + * @param isTopSelect True only for top-most select to store rowid columns in context only for the final list of columns. + * @return true on success, false on any failure. + * + * Finds columns representing ROWID for the \p table and adds them to result columns and to the context. + */ + bool addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, + QHash >& rowIdColsMap, bool isTopSelect); + + /** + * @brief Adds the column to result columns list. + * @param core SELECT's core that keeps the result columns. + * @param table Table that the column is for. + * @param queryExecutorColumn Alias name for the column that will be used by the query executor. + * @param realColumn Actual column name in the database. + * @return true on success, false on any failure. + * + * Adds given column to the result column list in the SELECT statement. + */ + bool addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, const QString& queryExecutorColumn, + const QString& realColumn); + + /** + * @brief Adds all necessary ROWID columns to result columns. + * @param select SELECT that keeps result columns. + * @param ok[out] Reference to a flag for telling if the method was executed successly (true), or not (false). + * @param isTopSelect True only for top-most select call of this method, so the list of rowid columns is stored + * only basing on this select (and rowid mappind for it), not all subqueries. This is to avoid redundant rowid columns in context + * in case of subselects. + * @return Mapping for every table mentioned in the SELECT with map of ROWID columns for the table. + * The column map is a query_executor_alias to real_database_column_name. + * + * Adds ROWID columns for all tables mentioned in result columns of the \p select. + */ + QHash> addRowIdForTables(SqliteSelect* select, bool& ok, bool isTopSelect = true); + + /** + * @brief Extracts all subselects used in the SELECT. + * @param core SELECT's core to extract subselects from. + * @return List of subselects. + * + * Extracts only subselects of given select core, but not recurrently. + * As it works on the SELECT's core, it means that it's not applicable for compound selects. + */ + QList getSubSelects(SqliteSelect::Core* core); + + /** + * @brief Provides list of columns representing ROWID for the table. + * @param table Table to get ROWID columns for. + * @return Map of query executor alias to real database column name. + */ + QHash getNextColNames(const SelectResolver::Table& table); +}; + +#endif // QUERYEXECUTORADDROWIDS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.cpp new file mode 100644 index 0000000..0df0445 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.cpp @@ -0,0 +1,16 @@ +#include "queryexecutorattaches.h" +#include "dbattacher.h" +#include "sqlitestudio.h" +#include + +bool QueryExecutorAttaches::exec() +{ + QScopedPointer attacher(SQLITESTUDIO->createDbAttacher(db)); + if (!attacher->attachDatabases(context->parsedQueries)) + return false; + + context->dbNameToAttach = attacher->getDbNameToAttach(); + updateQueries(); + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.h new file mode 100644 index 0000000..b6346fb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.h @@ -0,0 +1,28 @@ +#ifndef QUERYEXECUTORATTACHES_H +#define QUERYEXECUTORATTACHES_H + +#include "queryexecutorstep.h" +#include "parser/token.h" +#include + +/** + * @brief Checks for any databases required to attach and attaches them. + * + * If the query contains any name that is identified to be name of database registered in DbManager, + * then that database gets attached to the current database (the one that we execute query on) + * and its attach name is stored in the query executor context, so all attached databases + * can be later detached. + * + * This step accomplishes a transparent database attaching feature of SQLiteStudio. + * + * @see DbAttacher + */ +class QueryExecutorAttaches : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORATTACHES_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp new file mode 100644 index 0000000..fceea3f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp @@ -0,0 +1,99 @@ +#include "queryexecutorcellsize.h" +#include + +bool QueryExecutorCellSize::exec() +{ + if (queryExecutor->getDataLengthLimit() < 0) + return true; + + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + foreach (SqliteSelect::Core* core, select->coreSelects) + { + if (!applyDataLimit(select.data(), core)) + return false; + } + + updateQueries(); + return true; +} + +bool QueryExecutorCellSize::applyDataLimit(SqliteSelect* select, SqliteSelect::Core* core) +{ + if (core->tokensMap["selcollist"].size() == 0) + { + qCritical() << "No 'selcollist' in Select::Core. Cannot apply cell size limits."; + return false; + } + + bool first = true; + TokenList tokens; + + foreach (const QueryExecutor::ResultRowIdColumnPtr& col, context->rowIdColumns) + { + if (!first) + tokens += getSeparatorTokens(); + + tokens += getNoLimitTokens(col); + first = false; + } + + foreach (const QueryExecutor::ResultColumnPtr& col, context->resultColumns) + { + if (!first) + tokens += getSeparatorTokens(); + + tokens += getLimitTokens(col); + first = false; + } + + // Wrapping original select with new select with limited columns + select->tokens = wrapSelect(select->tokens, tokens); + + return true; +} + +TokenList QueryExecutorCellSize::getLimitTokens(const QueryExecutor::ResultColumnPtr& resCol) +{ + TokenList newTokens; + newTokens << TokenPtr::create(Token::OTHER, "substr") + << TokenPtr::create(Token::PAR_LEFT, "(") + << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias) + << TokenPtr::create(Token::OPERATOR, ",") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::INTEGER, "1") + << TokenPtr::create(Token::OPERATOR, ",") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::INTEGER, QString::number(queryExecutor->getDataLengthLimit())) + << TokenPtr::create(Token::PAR_RIGHT, ")") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::KEYWORD, "AS") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias); + return newTokens; +} + +TokenList QueryExecutorCellSize::getNoLimitTokens(const QueryExecutor::ResultRowIdColumnPtr& resCol) +{ + TokenList newTokens; + bool first = true; + foreach (const QString& col, resCol->queryExecutorAliasToColumn.keys()) + { + if (!first) + newTokens += getSeparatorTokens(); + + newTokens << TokenPtr::create(Token::OTHER, col); + first = false; + } + return newTokens; +} + +TokenList QueryExecutorCellSize::getSeparatorTokens() +{ + TokenList newTokens; + newTokens << TokenPtr::create(Token::OPERATOR, ","); + newTokens << TokenPtr::create(Token::SPACE, " "); + return newTokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h new file mode 100644 index 0000000..c174c69 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h @@ -0,0 +1,62 @@ +#ifndef QUERYEXECUTORCELLSIZE_H +#define QUERYEXECUTORCELLSIZE_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies per-cell byte size limit to the query. + * + * Size of data extracted for each cell is limited in order to avoid huge memory use + * when the database contains column with like 500MB values per row and the query + * returns for example 100 rows. + * + * This is accomplished by wrapping all result columns (except ROWID columns) with substr() SQL function. + * + * SQLiteStudio limits each column to SqlQueryModel::cellDataLengthLimit when displaying + * data in SqlQueryView. + * + * This feature is disabled by default in QueryExecutor and has to be enabled by defining + * QueryExecutor::setDataLengthLimit(). + */ +class QueryExecutorCellSize : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + /** + * @brief Applies limit function to all result columns in given SELECT. + * @param select Select that we want to limit. + * @param core Select's core that we want to limit. + * @return true on success, false on failure. + * + * This method is called for each core in the \p select. + */ + bool applyDataLimit(SqliteSelect* select, SqliteSelect::Core* core); + + /** + * @brief Generates tokens that will return limited value of the result column. + * @param resCol Result column to wrap. + * @return List of tokens. + */ + TokenList getLimitTokens(const QueryExecutor::ResultColumnPtr& resCol); + + /** + * @brief Generates tokens that will return unlimited value of the ROWID result column. + * @param resCol ROWID result column. + * @return List of tokens. + */ + TokenList getNoLimitTokens(const QueryExecutor::ResultRowIdColumnPtr& resCol); + + /** + * @brief Generates tokens representing result columns separator. + * @return List of tokens. + * + * Result columns separator tokens are just a period and a space. + */ + TokenList getSeparatorTokens(); +}; + +#endif // QUERYEXECUTORCELLSIZE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp new file mode 100644 index 0000000..02f90b2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp @@ -0,0 +1,256 @@ +#include "queryexecutorcolumns.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include "parser/parsererror.h" +#include + +// TODO need to test if attach name resolving works here + +bool QueryExecutorColumns::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + { + context->editionForbiddenReasons << QueryExecutor::EditionForbiddenReason::NOT_A_SELECT; + return true; + } + + // Resolving result columns of the select + SelectResolver resolver(db, queryExecutor->getOriginalQuery(), context->dbNameToAttach); + resolver.resolveMultiCore = true; + QList columns = resolver.resolve(select.data()).first(); + + if (resolver.hasErrors()) + { + qWarning() << "SelectResolver could not resolve the SELECT properly:" << resolver.getErrors().join("\n"); + return false; + } + + if (columns.size() == 0) + { + qWarning() << "SelectResolver could not resolve any column. Probably wrong table name entered by user, or something like that."; + return false; + } + + // Deleting old result columns and defining new ones + SqliteSelect::Core* core = select->coreSelects.first(); + for (SqliteSelect::Core::ResultColumn* resCol : core->resultColumns) + delete resCol; + + core->resultColumns.clear(); + + // Count total rowId columns + int rowIdColCount = 0; + for (const QueryExecutor::ResultRowIdColumnPtr& rowIdCol : context->rowIdColumns) + rowIdColCount += rowIdCol->queryExecutorAliasToColumn.size(); + + // Defining result columns + QueryExecutor::ResultColumnPtr resultColumn; + SqliteSelect::Core::ResultColumn* resultColumnForSelect = nullptr; + bool isRowIdColumn = false; + int i = 0; + for (const SelectResolver::Column& col : columns) + { + // Convert column to QueryExecutor result column + resultColumn = getResultColumn(col); + + // Adding new result column to the query + isRowIdColumn = (i < rowIdColCount); + resultColumnForSelect = getResultColumnForSelect(resultColumn, col, isRowIdColumn); + if (!resultColumnForSelect) + return false; + + resultColumnForSelect->setParent(core); + core->resultColumns << resultColumnForSelect; + + if (!isRowIdColumn) + context->resultColumns << resultColumn; // store it in context for later usage by any step + + i++; + } + + // Update query + select->rebuildTokens(); + wrapWithAliasedColumns(select.data()); + updateQueries(); + +// qDebug() << context->processedQuery; + + return true; +} + +QueryExecutor::ResultColumnPtr QueryExecutorColumns::getResultColumn(const SelectResolver::Column &resolvedColumn) +{ + QueryExecutor::ResultColumnPtr resultColumn = QueryExecutor::ResultColumnPtr::create(); + if (resolvedColumn.type == SelectResolver::Column::OTHER) + { + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::EXPRESSION; + resultColumn->displayName = resolvedColumn.displayName; + resultColumn->column = resolvedColumn.column; + resultColumn->alias = resolvedColumn.alias; + resultColumn->expression = true; + resultColumn->queryExecutorAlias = getNextColName(); + } + else + { + if (isSystemTable(resolvedColumn.table)) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::SYSTEM_TABLE; + + if (resolvedColumn.flags & SelectResolver::FROM_COMPOUND_SELECT) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::COMPOUND_SELECT; + + if (resolvedColumn.flags & SelectResolver::FROM_GROUPED_SELECT) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::GROUPED_RESULTS; + + if (resolvedColumn.flags & SelectResolver::FROM_DISTINCT_SELECT) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::DISTINCT_RESULTS; + + resultColumn->database = resolvedColumn.originalDatabase; + resultColumn->table = resolvedColumn.table; + resultColumn->column = resolvedColumn.column; + resultColumn->tableAlias = resolvedColumn.tableAlias; + resultColumn->alias = resolvedColumn.alias; + resultColumn->displayName = resolvedColumn.displayName; + + if (isRowIdColumnAlias(resultColumn->alias)) + { + resultColumn->queryExecutorAlias = resultColumn->alias; + } + else + { + resultColumn->queryExecutorAlias = getNextColName(); + } + } + return resultColumn; +} + +SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect(const QueryExecutor::ResultColumnPtr& resultColumn, const SelectResolver::Column& col, bool rowIdColumn) +{ + SqliteSelect::Core::ResultColumn* selectResultColumn = new SqliteSelect::Core::ResultColumn(); + + QString colString = resultColumn->column; + if (!resultColumn->expression) + colString = wrapObjIfNeeded(colString, dialect); + + Parser parser(dialect); + SqliteExpr* expr = parser.parseExpr(colString); + if (!expr) + { + qWarning() << "Could not parse result column expr:" << colString; + if (parser.getErrors().size() > 0) + qWarning() << "The error was:" << parser.getErrors().first()->getFrom() << ":" << parser.getErrors().first()->getMessage(); + + return nullptr; + } + + expr->setParent(selectResultColumn); + selectResultColumn->expr = expr; + + if (!(col.flags & SelectResolver::FROM_ANONYMOUS_SELECT)) // anonymous subselect will result in no prefixes for result column + { + if (!resultColumn->tableAlias.isNull()) + { + selectResultColumn->expr->table = resultColumn->tableAlias; + } + else if (!resultColumn->table.isNull()) + { + if (!resultColumn->database.isNull()) + { + if (context->dbNameToAttach.containsLeft(resultColumn->database, Qt::CaseInsensitive)) + selectResultColumn->expr->database = context->dbNameToAttach.valueByLeft(resultColumn->database, Qt::CaseInsensitive); + else + selectResultColumn->expr->database = resultColumn->database; + } + + selectResultColumn->expr->table = resultColumn->table; + } + } + + if (!col.alias.isNull()) + { + selectResultColumn->asKw = true; + selectResultColumn->alias = col.alias; + } + else if (rowIdColumn || resultColumn->expression) + { + selectResultColumn->asKw = true; + selectResultColumn->alias = resultColumn->queryExecutorAlias; + } + + return selectResultColumn; +} + +QString QueryExecutorColumns::resolveAttachedDatabases(const QString &dbName) +{ + if (context->dbNameToAttach.containsRight(dbName, Qt::CaseInsensitive)) + return context->dbNameToAttach.valueByRight(dbName, Qt::CaseInsensitive); + + return dbName; +} + +bool QueryExecutorColumns::isRowIdColumnAlias(const QString& alias) +{ + foreach (QueryExecutor::ResultRowIdColumnPtr rowIdColumn, context->rowIdColumns) + { + if (rowIdColumn->queryExecutorAliasToColumn.keys().contains(alias)) + return true; + } + return false; +} + +void QueryExecutorColumns::wrapWithAliasedColumns(SqliteSelect* select) +{ + // Wrap everything in a surrounding SELECT and given query executor alias to all columns this time + TokenList sepTokens; + sepTokens << TokenPtr::create(Token::OPERATOR, ",") << TokenPtr::create(Token::SPACE, " "); + + bool first = true; + TokenList outerColumns; + for (const QueryExecutor::ResultRowIdColumnPtr& rowIdColumn : context->rowIdColumns) + { + for (const QString& alias : rowIdColumn->queryExecutorAliasToColumn.keys()) + { + if (!first) + outerColumns += sepTokens; + + outerColumns << TokenPtr::create(Token::OTHER, alias); + first = false; + } + } + + QStringList columnNamesUsed; + QString baseColName; + QString colName; + static_qstring(colNameTpl, "%1:%2"); + for (const QueryExecutor::ResultColumnPtr& resCol : context->resultColumns) + { + if (!first) + outerColumns += sepTokens; + + // If alias was given, we use it. If it was anything but expression, we also use its display name, + // because it's explicit column (no matter if from table, or table alias). + baseColName = QString(); + if (!resCol->alias.isNull()) + baseColName = resCol->alias; + else if (!resCol->expression) + baseColName = resCol->column; + + if (!baseColName.isNull()) + { + colName = baseColName; + for (int i = 1; columnNamesUsed.contains(colName, Qt::CaseInsensitive); i++) + colName = colNameTpl.arg(resCol->column, QString::number(i)); + + columnNamesUsed << colName; + outerColumns << TokenPtr::create(Token::OTHER, wrapObjIfNeeded(colName, dialect)); + outerColumns << TokenPtr::create(Token::SPACE, " "); + outerColumns << TokenPtr::create(Token::KEYWORD, "AS"); + outerColumns << TokenPtr::create(Token::SPACE, " "); + } + outerColumns << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias); + first = false; + } + + //QString t = outerColumns.detokenize(); // keeping it for debug purposes + select->tokens = wrapSelect(select->tokens, outerColumns); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h new file mode 100644 index 0000000..3f90311 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h @@ -0,0 +1,72 @@ +#ifndef QUERYEXECUTORCOLUMNS_H +#define QUERYEXECUTORCOLUMNS_H + +#include "queryexecutorstep.h" +#include "selectresolver.h" + +/** + * @brief Assigns unique alias names for all result columns. + * + * This step replaces result columns of the SELECT query. + * It's performed only if last query is the SELECT, otherwise it does nothing. + * + * It works on subselects first, then goes towards outer SELECTs. + * + * Star operator ("all columns") is replaced by list of columns and each column gets alias. + * + * If result column comes from subselect and the subselect was already covered by this step, + * then the column does not get new alias, instead the existing one is used. + * + * While generating alias names, this step also finds out details about columns: source database, source table + * column contraints, etc. Those informations are stored using generated alias name as a key. + * + * Some columns can be defined as not editable, because of various reasons: QueryExecutor::ColumnEditionForbiddenReason. + * Those reasons are defined in this step. + */ +class QueryExecutorColumns : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + + /** + * @brief Transforms SelectResolver's columns into QueryExecutor's columns. + * @param resolvedColumn Result columns resolved by SelectResolver. + * @return Converted column. + * + * QueryExecutor understands different model of result columns than SelectResolver. + * Converted columns are later used by other steps and it's also returned from QueryExecutor as an information + * about result columns of the query. + */ + QueryExecutor::ResultColumnPtr getResultColumn(const SelectResolver::Column& resolvedColumn); + + /** + * @brief Generates result column object with proper alias name. + * @param resultColumn Original result column from the query. + * @param col Original result column as resolved by SelectResolver. + * @param rowIdColumn Indicates if this is a call for ROWID column added by QueryExecutorRowId step. + * @return Result column object ready for rebuilding tokens and detokenizing. + */ + SqliteSelect::Core::ResultColumn* getResultColumnForSelect(const QueryExecutor::ResultColumnPtr& resultColumn, const SelectResolver::Column& col, bool rowIdColumn); + + /** + * @brief Translates attach name into database name. + * @param dbName Attach name. + * @return Database name as registered in DbManager, or \p dbName if given name was not resolved to any registered database. + */ + QString resolveAttachedDatabases(const QString& dbName); + + /** + * @brief Checks if given alias name belongs to ROWID result column. + * @param alias Alias name to check. + * @return true if the alias belongs to ROWID column, or false otherwise. + */ + bool isRowIdColumnAlias(const QString& alias); + + void wrapWithAliasedColumns(SqliteSelect* select); +}; + +#endif // QUERYEXECUTORCOLUMNS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.cpp new file mode 100644 index 0000000..f51bf34 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.cpp @@ -0,0 +1,22 @@ +#include "queryexecutorcountresults.h" +#include "parser/ast/sqlitequery.h" +#include "db/queryexecutor.h" +#include +#include + +bool QueryExecutorCountResults::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + { + // No return rows, but we're good to go. + // Pragma and Explain statements use "rows affected" for number of rows. + return true; + } + + QString countSql = "SELECT count(*) AS cnt FROM ("+select->detokenize()+");"; + context->countingQuery = countSql; + + // qDebug() << "count sql:" << countSql; + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.h new file mode 100644 index 0000000..654433a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.h @@ -0,0 +1,19 @@ +#ifndef QUERYEXECUTORCOUNTRESULTS_H +#define QUERYEXECUTORCOUNTRESULTS_H + +#include "queryexecutorstep.h" + +/** + * @brief Defines counting query string. + * + * @see QueryExecutor::countResults() + */ +class QueryExecutorCountResults : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORCOUNTRESULTS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp new file mode 100644 index 0000000..9a5c1c2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp @@ -0,0 +1,32 @@ +#include "queryexecutordatasources.h" +#include "parser/ast/sqliteselect.h" +#include "selectresolver.h" + +bool QueryExecutorDataSources::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + if (select->coreSelects.size() > 1) // compound selects might have different collection of tables + return true; + + if (select->coreSelects.first()->valuesMode) + return true; + + SelectResolver resolver(db, select->tokens.detokenize()); + resolver.resolveMultiCore = false; // multicore subselects result in not editable columns, skip them + + SqliteSelect::Core* core = select->coreSelects.first(); + QSet tables = resolver.resolveTables(core); + foreach (SelectResolver::Table resolvedTable, tables) + { + QueryExecutor::SourceTablePtr table = QueryExecutor::SourceTablePtr::create(); + table->database = resolvedTable.database; + table->table = resolvedTable.table; + table->alias = resolvedTable.alias; + context->sourceTables << table; + } + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.h new file mode 100644 index 0000000..26650aa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.h @@ -0,0 +1,24 @@ +#ifndef QUERYEXECUTORDATASOURCES_H +#define QUERYEXECUTORDATASOURCES_H + +#include "queryexecutorstep.h" + +/** + * @brief Finds all source tables. + * + * Finds all source tables for the SELECT query (if it's the last query in the query string) + * and stores them in QueryExecutor::Context::sourceTables. They are later provided by QueryExecutor::getSourceTables() + * as a meta information about data sources. + * + * Source tables are tables that result columns come from. If there's multiple columns selected + * from single table, only single table is resolved. + */ +class QueryExecutorDataSources : public QueryExecutorStep +{ + Q_OBJECT + public: + bool exec(); + +}; + +#endif // QUERYEXECUTORDATASOURCES_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.cpp new file mode 100644 index 0000000..c3c8a5c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.cpp @@ -0,0 +1,26 @@ +#include "queryexecutordetectschemaalter.h" + +bool QueryExecutorDetectSchemaAlter::exec() +{ + for (SqliteQueryPtr query : context->parsedQueries) + { + switch (query->queryType) + { + case SqliteQueryType::AlterTable: + case SqliteQueryType::CreateIndex: + case SqliteQueryType::CreateTable: + case SqliteQueryType::CreateTrigger: + case SqliteQueryType::CreateView: + case SqliteQueryType::DropIndex: + case SqliteQueryType::DropTable: + case SqliteQueryType::DropTrigger: + case SqliteQueryType::DropView: + case SqliteQueryType::CreateVirtualTable: + context->schemaModified = true; + break; + default: + break; + } + } + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.h new file mode 100644 index 0000000..760513e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.h @@ -0,0 +1,14 @@ +#include "queryexecutorstep.h" + +#ifndef QUERYEXECUTORDETECTSCHEMAALTER_H +#define QUERYEXECUTORDETECTSCHEMAALTER_H + +class QueryExecutorDetectSchemaAlter : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORDETECTSCHEMAALTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp new file mode 100644 index 0000000..7e0abe5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp @@ -0,0 +1,149 @@ +#include "queryexecutorexecute.h" +#include "db/sqlerrorcodes.h" +#include "db/queryexecutor.h" +#include "parser/ast/sqlitequery.h" +#include "parser/lexer.h" +#include "parser/ast/sqlitecreatetable.h" +#include "datatype.h" +#include +#include +#include + +bool QueryExecutorExecute::exec() +{ + qDebug() << "q:" << context->processedQuery; + + startTime = QDateTime::currentMSecsSinceEpoch(); + return executeQueries(); +} + +void QueryExecutorExecute::provideResultColumns(SqlQueryPtr results) +{ + QueryExecutor::ResultColumnPtr resCol; + foreach (const QString& colName, results->getColumnNames()) + { + resCol = QueryExecutor::ResultColumnPtr::create(); + resCol->displayName = colName; + context->resultColumns << resCol; + } +} + +bool QueryExecutorExecute::executeQueries() +{ + QHash bindParamsForQuery; + SqlQueryPtr results; + + Db::Flags flags; + if (context->preloadResults) + flags |= Db::Flag::PRELOAD; + + int queryCount = context->parsedQueries.size(); + for (const SqliteQueryPtr& query : context->parsedQueries) + { + bindParamsForQuery = getBindParamsForQuery(query); + results = db->prepare(query->detokenize()); + results->setArgs(bindParamsForQuery); + results->setFlags(flags); + + queryCount--; + if (queryCount == 0) // last query? + setupSqlite2ColumnDataTypes(results); + + results->execute(); + + if (results->isError()) + { + handleFailResult(results); + return false; + } + } + handleSuccessfulResult(results); + return true; +} + +void QueryExecutorExecute::handleSuccessfulResult(SqlQueryPtr results) +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->coreSelects.size() > 1 || select->explain) + { + // In this case, the "Columns" step didn't provide result columns. + // We need to do it here, basing on actual results. + provideResultColumns(results); + } + + context->executionTime = QDateTime::currentMSecsSinceEpoch() - startTime; + context->rowsAffected = results->rowsAffected(); + + // For PRAGMA and EXPLAIN we simply count results for rows returned + SqliteQueryPtr lastQuery = context->parsedQueries.last(); + if (lastQuery->queryType != SqliteQueryType::Select || lastQuery->explain) + context->rowsCountingRequired = true; + + if (context->resultsHandler) + { + context->resultsHandler(results); + context->resultsHandler = nullptr; + } + + context->executionResults = results; +} + +void QueryExecutorExecute::handleFailResult(SqlQueryPtr results) +{ + if (!results->isInterrupted()) + { + qWarning() << "Could not execute query with smart method:" << queryExecutor->getOriginalQuery() + << "\nError message:" << results->getErrorText() + << "\nSkipping smart execution."; + } +} + +QHash QueryExecutorExecute::getBindParamsForQuery(SqliteQueryPtr query) +{ + QHash queryParams; + QStringList bindParams = query->tokens.filter(Token::BIND_PARAM).toStringList(); + foreach (const QString& bindParam, bindParams) + { + if (context->queryParameters.contains(bindParam)) + queryParams.insert(bindParam, context->queryParameters[bindParam]); + } + return queryParams; +} + +void QueryExecutorExecute::setupSqlite2ColumnDataTypes(SqlQueryPtr results) +{ + Sqlite2ColumnDataTypeHelper* sqlite2Helper = dynamic_cast(results.data()); + if (!sqlite2Helper) + return; + + QPair key; + SqliteCreateTablePtr createTable; + + SchemaResolver resolver(db); + QHash,SqliteCreateTablePtr> tables; + for (QueryExecutor::SourceTablePtr tab : context->sourceTables) + { + if (tab->table.isNull()) + continue; + + key = QPair(tab->database, tab->table); + createTable = resolver.getParsedObject(tab->database, tab->table, SchemaResolver::TABLE).dynamicCast(); + tables[key] = createTable; + } + + sqlite2Helper->clearBinaryTypes(); + + SqliteCreateTable::Column* column = nullptr; + int idx = -1 + context->rowIdColumns.size(); + for (QueryExecutor::ResultColumnPtr resCol : context->resultColumns) + { + idx++; + key = QPair(resCol->database, resCol->table); + if (!tables.contains(key)) + continue; + + column = tables[key]->getColumn(resCol->column); + if (column->type && DataType::isBinary(column->type->name)) + sqlite2Helper->setBinaryType(idx); + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.h new file mode 100644 index 0000000..a88bf56 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.h @@ -0,0 +1,85 @@ +#ifndef QUERYEXECUTOREXECUTE_H +#define QUERYEXECUTOREXECUTE_H + +#include "queryexecutorstep.h" +#include + +/** + * @brief Executes query in current form. + * + * Executes query synchronously (since entire query executor works in another thread anyway). + * After execution is finished it provides information about how long it took, whether there was + * an error, and how many rows were affected/returned. + * + * The query string may contain many queries separated by semicolon and this step will split + * them correctly, then execute one-by-one. Results are loaded only from last query execution. + * + * If the last query was not processed by QueryExecutorColumns step, then this step + * will provide list result column names basing on what names returned SQLite. + * + * For PRAGMA and EXPLAIN statements rows returned are not accurate + * and QueryExecutor::Context::rowsCountingRequired is set to true. + */ +class QueryExecutorExecute : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + /** + * @brief Gives list of column names as SQLite returned them. + * @param results Execution results. + */ + void provideResultColumns(SqlQueryPtr results); + + /** + * @brief Executes the query. + * @return true on success, false on failure. + * + * Stops on first error and in that case rolls back transaction. + * + * If QueryExecutor::Context::preloadResults is true, then also Db::Flag::PRELOAD + * is appended to execution flags. + */ + bool executeQueries(); + + /** + * @brief Extracts meta information from results. + * @param results Execution results. + * + * Meta information includes rows affected, execution time, etc. + */ + void handleSuccessfulResult(SqlQueryPtr results); + + /** + * @brief Handles failed execution. + * @param results Execution results. + * + * Currently this method doesn't do much. It just checks whether execution + * error was caused by call to Db::interrupt(), or not and if not, + * then the warning is logged about it and executor falls back to simple + * execution method. + */ + void handleFailResult(SqlQueryPtr results); + + /** + * @brief Prepares parameters for query execution. + * @param query Query to be executed. + * @return Map of parameters for the query. + * + * It generates parameters basing on what are parameter placeholders in the query + * and what are parameter values available in QueryExecutor::Context::queryParameters. + */ + QHash getBindParamsForQuery(SqliteQueryPtr query); + + /** + * @brief Number of milliseconds since 1970 at execution start moment. + */ + qint64 startTime; + + void setupSqlite2ColumnDataTypes(SqlQueryPtr results); +}; + +#endif // QUERYEXECUTOREXECUTE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.cpp new file mode 100644 index 0000000..117022e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.cpp @@ -0,0 +1,28 @@ +#include "queryexecutorexplainmode.h" + +bool QueryExecutorExplainMode::exec() +{ + if (!context->explainMode) + return true; // explain mode disabled + + SqliteQueryPtr lastQuery = context->parsedQueries.last(); + + if (!lastQuery) + return true; + + // If last query wasn't in explain mode, switch it on + if (!lastQuery->explain) + { + lastQuery->explain = true; + lastQuery->tokens.prepend(TokenPtr::create(Token::SPACE, " ")); + lastQuery->tokens.prepend(TokenPtr::create(Token::KEYWORD, "EXPLAIN")); + } + + // Limit queries to only last one + context->parsedQueries.clear(); + context->parsedQueries << lastQuery; + + updateQueries(); + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.h new file mode 100644 index 0000000..bd806c7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.h @@ -0,0 +1,18 @@ +#ifndef QUERYEXECUTOREXPLAINMODE_H +#define QUERYEXECUTOREXPLAINMODE_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies QueryExecutor::Context::explainMode to the query. + * + * If explain mode is enabled, it just prepends "EXPLAIN" before the query. + */ +class QueryExecutorExplainMode : public QueryExecutorStep +{ + Q_OBJECT + public: + bool exec(); +}; + +#endif // QUERYEXECUTOREXPLAINMODE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp new file mode 100644 index 0000000..af1d7a6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp @@ -0,0 +1,30 @@ +#include "queryexecutorlimit.h" +#include "parser/ast/sqlitelimit.h" +#include + +bool QueryExecutorLimit::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + int page = queryExecutor->getPage(); + if (page < 0) + return true; // no paging requested + + if (select->tokens.size() < 1) + return true; // shouldn't happen, but if happens, quit gracefully + + quint64 limit = queryExecutor->getResultsPerPage(); + quint64 offset = limit * page; + + // The original query is last, so if it contained any %N strings, + // they won't be replaced. + static_qstring(selectTpl, "SELECT * FROM (%1) LIMIT %2 OFFSET %3"); + QString newSelect = selectTpl.arg(select->detokenize(), QString::number(limit), QString::number(offset)); + + int begin = select->tokens.first()->start; + int length = select->tokens.last()->end - select->tokens.first()->start + 1; + context->processedQuery = context->processedQuery.replace(begin, length, newSelect); + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.h new file mode 100644 index 0000000..56714b4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.h @@ -0,0 +1,22 @@ +#ifndef QUERYEXECUTORLIMIT_H +#define QUERYEXECUTORLIMIT_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies row count limit to the query. + * + * If row count limit is enabled (QueryExecutor::Context::setPage() + * and QueryExecutor::Context::setResultsPerPage), then the SELECT query + * is wrapped with another SELECT which defines it's own LIMIT and OFFSET + * basing on the page and the results per page parameters. + */ +class QueryExecutorLimit : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORLIMIT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.cpp new file mode 100644 index 0000000..09e35ed --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.cpp @@ -0,0 +1,79 @@ +#include "queryexecutororder.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include + +bool QueryExecutorOrder::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + QueryExecutor::SortList sortOrder = queryExecutor->getSortOrder(); + if (sortOrder.size() == 0) + return true; // no sorting requested + + if (select->tokens.size() < 1) + return true; // shouldn't happen, but if happens, leave gracefully + + TokenList tokens = getOrderTokens(sortOrder); + if (tokens.size() == 0) + return false; + + static_qstring(selectTpl, "SELECT * FROM (%1) ORDER BY %2"); + QString newSelect = selectTpl.arg(select->detokenize(), tokens.detokenize()); + + Parser parser(dialect); + if (!parser.parse(newSelect) || parser.getQueries().size() == 0) + { + qWarning() << "Could not parse SELECt after applying order. Tried to parse query:\n" << newSelect; + return false; + } + + context->parsedQueries.removeLast(); + context->parsedQueries << parser.getQueries().first(); + + updateQueries(); + return true; +} + +TokenList QueryExecutorOrder::getOrderTokens(const QueryExecutor::SortList& sortOrder) +{ + TokenList tokens; + QueryExecutor::ResultColumnPtr resCol; + bool next = false; + for (const QueryExecutor::Sort& sort : sortOrder) + { + if (sort.column >= context->resultColumns.size()) + { + qCritical() << "There is less result columns in query executor context than index of requested sort column"; + return TokenList(); + } + + if (next) + { + tokens << TokenPtr::create(Token::OPERATOR, ","); + tokens << TokenPtr::create(Token::SPACE, " "); + } + else + next = true; + + resCol = context->resultColumns[sort.column]; + + tokens << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias); + tokens << TokenPtr::create(Token::SPACE, " "); + + if (sort.order == QueryExecutor::Sort::DESC) + { + tokens << TokenPtr::create(Token::KEYWORD, "DESC"); + tokens << TokenPtr::create(Token::SPACE, " "); + } + else + { + tokens << TokenPtr::create(Token::KEYWORD, "ASC"); + tokens << TokenPtr::create(Token::SPACE, " "); + } + } + + return tokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.h new file mode 100644 index 0000000..d321f9b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.h @@ -0,0 +1,30 @@ +#ifndef QUERYEXECUTORORDER_H +#define QUERYEXECUTORORDER_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies sorting to the query. + * + * Sorting is done by enclosing SELECT query with another SELECT + * query, but the outer one uses order defined in this step. + * + * The order is defined by QueryExecutor::setSortOrder(). + */ +class QueryExecutorOrder : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + /** + * @brief Generates tokens to sort by given column and order. + * @param sortOrder Definition of order to use. + * @return Tokens to append to the SELECT. + */ + TokenList getOrderTokens(const QueryExecutor::SortList& sortOrder); +}; + +#endif // QUERYEXECUTORORDER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.cpp new file mode 100644 index 0000000..cd91734 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.cpp @@ -0,0 +1,48 @@ +#include "queryexecutorparsequery.h" +#include "db/queryexecutor.h" +#include "parser/parser.h" +#include + +QueryExecutorParseQuery::QueryExecutorParseQuery(const QString& name) + : QueryExecutorStep() +{ + setObjectName(name); +} + +QueryExecutorParseQuery::~QueryExecutorParseQuery() +{ + if (parser) + delete parser; +} + +bool QueryExecutorParseQuery::exec() +{ + // Prepare parser + if (parser) + delete parser; + + parser = new Parser(dialect); + + // Do parsing + context->parsedQueries.clear(); + parser->parse(context->processedQuery); + if (parser->getErrors().size() > 0) + { + qWarning() << "QueryExecutorParseQuery:" << parser->getErrorString() << "\n" + << "Query parsed:" << context->processedQuery; + return false; + } + + if (parser->getQueries().size() == 0) + { + qWarning() << "No queries parsed in QueryExecutorParseQuery step."; + return false; + } + + context->parsedQueries = parser->getQueries(); + + // We never want the semicolon in last query, because the query could be wrapped with a SELECT + context->parsedQueries.last()->tokens.trimRight(Token::OPERATOR, ";"); + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.h new file mode 100644 index 0000000..6dc8ab5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.h @@ -0,0 +1,29 @@ +#ifndef QUERYEXECUTORPARSEQUERY_H +#define QUERYEXECUTORPARSEQUERY_H + +#include "queryexecutorstep.h" + +/** + * @brief Parses current query and stores results in the context. + * + * Parses QueryExecutor::Context::processedQuery and stores results + * in QueryExecutor::Context::parsedQueries. + * + * This is used after some changes were made to the query and next steps will + * require parsed representation of queries to be updated. + */ +class QueryExecutorParseQuery : public QueryExecutorStep +{ + Q_OBJECT + + public: + explicit QueryExecutorParseQuery(const QString& name); + ~QueryExecutorParseQuery(); + + bool exec(); + + private: + Parser* parser = nullptr; +}; + +#endif // QUERYEXECUTORPARSEQUERY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp new file mode 100644 index 0000000..94300a0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp @@ -0,0 +1,113 @@ +#include "queryexecutorreplaceviews.h" +#include "parser/ast/sqlitecreateview.h" +#include "schemaresolver.h" +#include + +QueryExecutorReplaceViews::~QueryExecutorReplaceViews() +{ + if (schemaResolver) + { + delete schemaResolver; + schemaResolver = nullptr; + } +} + +bool QueryExecutorReplaceViews::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + if (select->coreSelects.size() > 1) + return true; + + if (select->coreSelects.first()->distinctKw) + return true; + + replaceViews(select.data()); + updateQueries(); + + return true; +} + +void QueryExecutorReplaceViews::init() +{ + if (!schemaResolver) + schemaResolver = new SchemaResolver(db); +} + +QStringList QueryExecutorReplaceViews::getViews(const QString& database) +{ + QString dbName = database.isNull() ? "main" : database.toLower(); + if (views.contains(dbName)) + return views[dbName]; + + views[dbName] = schemaResolver->getViews(database); + return views[dbName]; +} + +SqliteCreateViewPtr QueryExecutorReplaceViews::getView(const QString& database, const QString& viewName) +{ + View view(database, viewName); + if (viewStatements.contains(view)) + return viewStatements[view]; + + SqliteQueryPtr query = schemaResolver->getParsedObject(database, viewName, SchemaResolver::VIEW); + if (!query) + return SqliteCreateViewPtr(); + + SqliteCreateViewPtr viewPtr = query.dynamicCast(); + if (!viewPtr) + return SqliteCreateViewPtr(); + + viewStatements[view] = viewPtr; + return viewPtr; +} + +void QueryExecutorReplaceViews::replaceViews(SqliteSelect* select) +{ + SqliteSelect::Core* core = select->coreSelects.first(); + + QStringList viewsInDatabase; + SqliteCreateViewPtr view; + + QList sources = core->getAllTypedStatements(); + foreach (SqliteSelect::Core::SingleSource* src, sources) + { + if (src->table.isNull()) + continue; + + viewsInDatabase = getViews(src->database); + if (!viewsInDatabase.contains(src->table, Qt::CaseInsensitive)) + continue; + + view = getView(src->database, src->table); + if (!view) + { + qWarning() << "Object" << src->database << "." << src->table + << "was identified to be a view, but could not get it's parsed representation."; + continue; + } + + src->select = view->select; + src->database = QString::null; + src->table = QString::null; + } + + select->rebuildTokens(); +} + +uint qHash(const QueryExecutorReplaceViews::View& view) +{ + return qHash(view.database + "." + view.view); +} + +QueryExecutorReplaceViews::View::View(const QString& database, const QString& view) : + database(database), view(view) +{ +} + +int QueryExecutorReplaceViews::View::operator==(const QueryExecutorReplaceViews::View& other) const +{ + return database == other.database && view == other.view; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h new file mode 100644 index 0000000..633b0bc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h @@ -0,0 +1,110 @@ +#ifndef QUERYEXECUTORREPLACEVIEWS_H +#define QUERYEXECUTORREPLACEVIEWS_H + +#include "queryexecutorstep.h" +#include "parser/ast/sqlitecreateview.h" + +/** + * @brief Replaces all references to views in query with SELECTs from those views. + * + * Replacing views with their SELECTs (as subselects) simplifies later tasks + * with the query. + */ +class QueryExecutorReplaceViews : public QueryExecutorStep +{ + Q_OBJECT + + public: + ~QueryExecutorReplaceViews(); + + bool exec(); + + protected: + void init(); + + private: + /** + * @brief View representation in context of QueryExecutorReplaceViews step. + */ + struct View + { + /** + * @brief Creates view. + * @param database Database of the view. + * @param view View name. + */ + View(const QString& database, const QString& view); + + /** + * @brief Database of the view. + */ + QString database; + + /** + * @brief View name. + */ + QString view; + + /** + * @brief Checks if it's the same view as the \p other. + * @param other Other view to compare. + * @return 1 if other view is the same one, or 0 otherwise. + * + * Views are equal if they have equal name and database. + */ + int operator==(const View& other) const; + }; + + friend uint qHash(const View& view); + + /** + * @brief Provides all views existing in the database. + * @param database Database name as typed in the query. + * @return List of view names. + * + * Uses internal cache (using views). + */ + QStringList getViews(const QString& database); + + /** + * @brief Reads view's DDL, parses it and returns results. + * @param database Database of the view. + * @param viewName View name. + * @return Parsed view or null pointer if view doesn't exist or could not be parsed. + * + * It uses internal cache (using viewStatements). + */ + SqliteCreateViewPtr getView(const QString& database, const QString& viewName); + + /** + * @brief Replaces views in the query with SELECT statements. + * @param select SELECT statement to replace views in. + * + * It explores the \p select looking for view names and replaces them with + * apropriate subselect queries, using getView() calls. + */ + void replaceViews(SqliteSelect* select); + + /** + * @brief Used for caching view list per database. + */ + QHash views; + + /** + * @brief Resolver used several time in this step. + * + * It's stored as member of this class, cause otherwise it would be created + * and deleted many times. Instead it's shared across all calls to resolve something + * from schema. + */ + SchemaResolver* schemaResolver = nullptr; + + /** + * @brief Used for caching parsed view statement. + */ + QHash viewStatements; +}; + +uint qHash(const QueryExecutorReplaceViews::View& view); + +#endif // QUERYEXECUTORREPLACEVIEWS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp new file mode 100644 index 0000000..d64ea2e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp @@ -0,0 +1,63 @@ +#include "queryexecutorstep.h" +#include "db/queryexecutor.h" +#include "common/unused.h" + +QueryExecutorStep::~QueryExecutorStep() +{ +} + +void QueryExecutorStep::init(QueryExecutor *queryExecutor, QueryExecutor::Context *context) +{ + this->queryExecutor = queryExecutor; + this->context = context; + db = queryExecutor->getDb(); + dialect = db->getDialect(); + init(); +} + +void QueryExecutorStep::updateQueries() +{ + QString newQuery; + foreach (SqliteQueryPtr query, context->parsedQueries) + { + newQuery += query->detokenize(); + newQuery += "\n"; + } + context->processedQuery = newQuery; +} + +QString QueryExecutorStep::getNextColName() +{ + return "ResCol_" + QString::number(context->colNameSeq++); +} + +SqliteSelectPtr QueryExecutorStep::getSelect() +{ + SqliteQueryPtr lastQuery = context->parsedQueries.last(); + if (lastQuery->queryType != SqliteQueryType::Select) + return SqliteSelectPtr(); + + return lastQuery.dynamicCast(); +} + +void QueryExecutorStep::init() +{ +} + +TokenList QueryExecutorStep::wrapSelect(const TokenList& selectTokens, const TokenList& resultColumnsTokens) +{ + TokenList oldSelectTokens = selectTokens; + oldSelectTokens.trimRight(Token::OPERATOR, ";"); + + TokenList newTokens; + newTokens << TokenPtr::create(Token::KEYWORD, "SELECT") + << TokenPtr::create(Token::SPACE, " "); + newTokens += resultColumnsTokens; + newTokens << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::KEYWORD, "FROM") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::PAR_LEFT, "("); + newTokens += oldSelectTokens; + newTokens << TokenPtr::create(Token::PAR_RIGHT, ")"); + return newTokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h new file mode 100644 index 0000000..a15ad9c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h @@ -0,0 +1,158 @@ +#ifndef QUERYEXECUTORSTEP_H +#define QUERYEXECUTORSTEP_H + +#include "db/sqlquery.h" +#include "parser/ast/sqliteselect.h" +#include "db/queryexecutor.h" +#include + +/** + * @brief Base class for all query executor steps. + * + * Query Executor step is a single step that query executor performs at a time + * during the "smart execution" (see QueryExecutor for details). + * + * Steps are created and queued in QueryExecutor::setupExecutionChain(). + * Order of steps execution is very important and should not be distrurbed, + * unless you know what you're doing. + * + * A step must implement one method: exec(). + * The implementation can access QueryExecutor instance that is running the step + * and also it can access common context for executor and all the steps. + * + * Steps usually introduce some modifications to the query, so the final execution + * can provide more meta-information, or works on limited set of data, etc. + * + * Steps can access common context to get parsed object of the current query. + * The current query is the query processed by previous steps and re-parsed after + * those modifications. Current query is also available in a string representation + * in the context. The original query string (before any modifications) is also + * available in the context. See QueryExecutor::Context for more. + * + * QueryExecutorStep provides several methods to help dealing with common routines, + * such as updating current query with modified query definition (updateQueries()), + * or extracting parsed SELECT (if the last query defined was the SELECT) object + * (getSelect()). When the step needs to add new result column, it can use + * getNextColName() to generate unique name. + * + * To access database object, that the query is executed on, use QueryExecutor::getDb(). + */ +class QueryExecutorStep : public QObject +{ + Q_OBJECT + + public: + /** + * @brief Cleans up resources. + */ + virtual ~QueryExecutorStep(); + + /** + * @brief Initializes step with a pointer to calling executor and a common context. + * @param queryExecutor Calling query executor. + * @param context Common context, shared among query executor and all steps. + * + * This method also calls virtual method init(), which can be used to one-time setup + * in derived step. + */ + void init(QueryExecutor* queryExecutor, QueryExecutor::Context* context); + + /** + * @brief Executes step routines. + * @return result of execution - successful or failed. + * + * The exec() method should execute all necessary routines required by this step. + * If it needs to execute anything on the database + * + * Once the false is returned by exec(), there should be no signal emitted by the step. + */ + virtual bool exec() = 0; + + protected: + /** + * @brief Updates QueryExecutor::Context::processedQuery string. + * + * Goes through all queries in QueryExecutor::Context::parsedQueries and detokenizes + * their tokens, concatenatins all results into QueryExecutor::Context::processedQuery. + * + * This should be called every time tokens of any parsed query were modified + * and you want those changes to be reflected in the processed query. + * + * See QueryExecutor::Context::processedQuery for more details; + */ + void updateQueries(); + + /** + * @brief Generates unique name for result column alias. + * @return Unique name. + * + * When a step needs to add another column to results, it can use this method + * to make sure that the name (alias) of this column will be unique across + * other result columns. + * + * QueryExecutorColumn step makes sure that every result column processed + * by query executor gets its own unique alias name. + * + * The getNextColName() whould be used only once per result column. When forwarding + * the result column from inner select to outer select, use the same alias name + * ad in the inner select. + */ + QString getNextColName(); + + /** + * @brief Extracts SELECT object from parsed queries. + * @return Parsed SELECT, or null pointer. + * + * This is a helper method. Since most of the logic in steps is required in regards + * of the last SELECT statement in all queries, this method comes handy. + * + * If the last statement in executed queries is the SELECT, then this method + * returns parsed object of that statement, otherwise it returns null pointer. + */ + SqliteSelectPtr getSelect(); + + /** + * @brief Custom initialization of the step. + * + * If a step needs some initial code to be executed, or some members to be initialized, + * this is the place to put it into. + */ + virtual void init(); + + /** + * @brief Puts the SELECT as a subselect. + * @param selectTokens All tokens of the original SELECT. + * @param resultColumnsTokens Result columns tokens for the new SELECT. + * @return New SELECT tokens. + * + * Original SELECT tokens are put into subselect of the new SELECT statement. New SELECT statement + * is built using given \p resultColumnTokens. + */ + TokenList wrapSelect(const TokenList& selectTokens, const TokenList& resultColumnsTokens); + + /** + * @brief Pointer to the calling executor. + */ + QueryExecutor* queryExecutor = nullptr; + + /** + * @brief Pointer to a shared context for all steps. + */ + QueryExecutor::Context* context = nullptr; + + /** + * @brief Database that all queries will be executed on. + * + * Defined by init(). + */ + Db* db = nullptr; + + /** + * @brief SQLite dialect of the database. + * + * Defined by init(). + */ + Dialect dialect; +}; + +#endif // QUERYEXECUTORSTEP_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.cpp new file mode 100644 index 0000000..8807178 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.cpp @@ -0,0 +1,26 @@ +#include "queryexecutorvaluesmode.h" + +bool QueryExecutorValuesMode::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + bool modified = false; + for (SqliteSelect::Core* core : select->coreSelects) + { + if (core->valuesMode) + { + core->valuesMode = false; + modified = true; + } + } + + if (modified) + { + select->rebuildTokens(); + updateQueries(); + } + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.h new file mode 100644 index 0000000..4037a5b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.h @@ -0,0 +1,14 @@ +#ifndef QUERYEXECUTORVALUESMODE_H +#define QUERYEXECUTORVALUESMODE_H + +#include "queryexecutorstep.h" + +class QueryExecutorValuesMode : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORVALUESMODE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp new file mode 100644 index 0000000..2565c2c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp @@ -0,0 +1,42 @@ +#include "queryexecutorwrapdistinctresults.h" + +bool QueryExecutorWrapDistinctResults::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + SqliteSelect::Core* core = select->coreSelects.first(); + + if (core->distinctKw || core->groupBy.size() > 0) + wrapSelect(select.data()); + + return true; +} + +void QueryExecutorWrapDistinctResults::wrapSelect(SqliteSelect* select) +{ + // Trim tokens from right side + TokenList origTokens = select->tokens; + origTokens.trimRight(); + + // Remove ; operator if present at the end + while (origTokens.last()->type == Token::OPERATOR && origTokens.last()->value == ";") + origTokens.removeLast(); + + // Create new list + TokenList tokens; + tokens << TokenPtr::create(Token::KEYWORD, "SELECT"); + tokens << TokenPtr::create(Token::SPACE, " "); + tokens << TokenPtr::create(Token::OPERATOR, "*"); + tokens << TokenPtr::create(Token::SPACE, " "); + tokens << TokenPtr::create(Token::KEYWORD, "FROM"); + tokens << TokenPtr::create(Token::SPACE, " "); + tokens << TokenPtr::create(Token::OPERATOR, "("); + tokens += origTokens; + tokens << TokenPtr::create(Token::OPERATOR, ")"); + tokens << TokenPtr::create(Token::OPERATOR, ";"); + + select->tokens = tokens; + updateQueries(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.h new file mode 100644 index 0000000..eeb94ef --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.h @@ -0,0 +1,33 @@ +#ifndef QUERYEXECUTORWRAPDISTINCTRESULTS_H +#define QUERYEXECUTORWRAPDISTINCTRESULTS_H + +#include "queryexecutorstep.h" + +/** + * @brief The QueryExecutorWrapDistinctResults class + * + * This step is necessary for DISTINCT and GROUP BY selects, + * because result columns are always limited by substr() function, + * which makes INT and TEXT types of data to be the same, which is false. + * For those cases, we need to put DISTINCT/GROUP BY into subselect, + * so it works on original result columns, then get the substr() from them. + * + * Processed query is stored in QueryExecutor::Context::processedQuery. + */ +class QueryExecutorWrapDistinctResults : public QueryExecutorStep +{ + Q_OBJECT + public: + bool exec(); + + private: + /** + * @brief Wraps single SELECT statement. + * @param select SELECT to wrap. + * + * Puts given \p select as subselect of a new SELECT. + */ + void wrapSelect(SqliteSelect* select); +}; + +#endif // QUERYEXECUTORWRAPDISTINCTRESULTS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.cpp new file mode 100644 index 0000000..f18a785 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.cpp @@ -0,0 +1,13 @@ +#include "db/sqlerrorcodes.h" + +// This is defined in sqlite3.h, but we don't want to make a dependency +// from coreSQLiteStudio to sqlite3.h just for this one constraint, +// while the coreSQLiteStudio doesn't depend on sqlite lib at all. +// This should not change anyway. It's a documented value. +#define SQLITE_INTERRUPT 9 + +bool SqlErrorCode::isInterrupted(int errorCode) +{ + // Luckily SQLITE_INTERRUPT has the same value defined in both SQLite versions. + return (errorCode == SqlErrorCode::INTERRUPTED || errorCode == SQLITE_INTERRUPT); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h new file mode 100644 index 0000000..12849cf --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h @@ -0,0 +1,39 @@ +#ifndef SQLERRORCODES_H +#define SQLERRORCODES_H + +#include "coreSQLiteStudio_global.h" + +/** + * @brief Custom SQL error codes. + * + * Those are custom error codes that can be returned by SqlResults::getErrorCode(). + * Usually error codes come from SQLite itself, but some errors can be generated + * by SQLiteStudio and for those cases this enum lists possible codes. + * + * Codes in this enum are not conflicting with error codes returned from SQLite. + */ +struct API_EXPORT SqlErrorCode +{ + enum + { + DB_NOT_OPEN = -1000, /**< Database was not open */ + QUERY_EXECUTOR_ERROR = -1001, /**< QueryExecutor error (its sophisticated logic encountered some problem) */ + PARSER_ERROR = -1002, /**< Parser class could not parse the query, because it was either invalid SQL, or bug in the Parser */ + INTERRUPTED = -1003, /**< Query execution was interrupted */ + INVALID_ARGUMENT = -1004, /**< Passed query argument was invalid (out of range, invalid format, etc.) */ + DB_NOT_DEFINED = -1005, /**< Database was not defined */ + OTHER_EXECUTION_ERROR = -1006 /**< Identifies other execution errors, see error message for details */ + }; + + /** + * @brief Tests if given error code means that execution was interrupted. + * @param errorCode Error code to test. + * @return true if the code represents interruption, or false otherwise. + * + * This method checks both SqlErrorCode::INTERRUPTED and SQLITE_INTERRUPT values, + * so if the code is either of them, it returns true. + */ + static bool isInterrupted(int errorCode); +}; + +#endif // SQLERRORCODES_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.cpp new file mode 100644 index 0000000..e7f6acd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.cpp @@ -0,0 +1,55 @@ +#include "sqlerrorresults.h" +#include "common/unused.h" + +SqlErrorResults::SqlErrorResults(int code, const QString& text) +{ + errText = text; + errCode = code; +} + +QString SqlErrorResults::getErrorText() +{ + return errText; +} + +int SqlErrorResults::getErrorCode() +{ + return errCode; +} + +QStringList SqlErrorResults::getColumnNames() +{ + return QStringList(); +} + +int SqlErrorResults::columnCount() +{ + return 0; +} + +qint64 SqlErrorResults::rowsAffected() +{ + return 0; +} + +SqlResultsRowPtr SqlErrorResults::nextInternal() +{ + return SqlResultsRowPtr(); +} + +bool SqlErrorResults::hasNextInternal() +{ + return false; +} + +bool SqlErrorResults::execInternal(const QList& args) +{ + UNUSED(args); + return false; +} + +bool SqlErrorResults::execInternal(const QHash& args) +{ + UNUSED(args); + return false; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.h new file mode 100644 index 0000000..f837245 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.h @@ -0,0 +1,48 @@ +#ifndef SQLERRORRESULTS_H +#define SQLERRORRESULTS_H + +#include "sqlquery.h" +#include + +/** + * @brief SqlResults implementation for returning errors. + * + * It's very simple implementation of SqlResults, which has hardcoded number of columns and rows (0). + * It has single constructor which accepts error code and error message, which are later + * returned from getErrorCode() and getErrorText(). + */ +class SqlErrorResults : public SqlQuery +{ + public: + /** + * @brief Creates error results with given code and message. + * @param code Error code. + * @param text Error message. + */ + SqlErrorResults(int code, const QString &text); + + QString getErrorText(); + int getErrorCode(); + QStringList getColumnNames(); + int columnCount(); + qint64 rowsAffected(); + + protected: + SqlResultsRowPtr nextInternal(); + bool hasNextInternal(); + bool execInternal(const QList& args); + bool execInternal(const QHash& args); + + private: + /** + * @brief Error message passed in constructor. + */ + QString errText; + + /** + * @brief errCode Error code passed in constructor. + */ + int errCode; +}; + +#endif // SQLERRORRESULTS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp new file mode 100644 index 0000000..4217711 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp @@ -0,0 +1,142 @@ +#include "sqlquery.h" +#include "db/sqlerrorcodes.h" + +SqlQuery::~SqlQuery() +{ +} + +bool SqlQuery::execute() +{ + if (queryArgs.type() == QVariant::Hash) + return execInternal(queryArgs.toHash()); + else + return execInternal(queryArgs.toList()); +} + +SqlResultsRowPtr SqlQuery::next() +{ + if (preloaded) + { + if (preloadedRowIdx >= preloadedData.size()) + return SqlResultsRowPtr(); + + return preloadedData[preloadedRowIdx++]; + } + return nextInternal(); +} + +bool SqlQuery::hasNext() +{ + if (preloaded) + return (preloadedRowIdx < preloadedData.size()); + + return hasNextInternal(); +} + +qint64 SqlQuery::rowsAffected() +{ + return affected; +} + +QList SqlQuery::getAll() +{ + if (!preloaded) + preload(); + + return preloadedData; +} + +void SqlQuery::preload() +{ + if (preloaded) + return; + + QList allRows; + while (hasNextInternal()) + allRows << nextInternal(); + + preloadedData = allRows; + preloaded = true; + preloadedRowIdx = 0; +} + +QVariant SqlQuery::getSingleCell() +{ + SqlResultsRowPtr row = next(); + if (row.isNull()) + return QVariant(); + + return row->value(0); +} + +bool SqlQuery::isError() +{ + return getErrorCode() != 0; +} + +bool SqlQuery::isInterrupted() +{ + return SqlErrorCode::isInterrupted(getErrorCode()); +} + +RowId SqlQuery::getInsertRowId() +{ + return insertRowId; +} + +qint64 SqlQuery::getRegularInsertRowId() +{ + return insertRowId["ROWID"].toLongLong(); +} + +QString SqlQuery::getQuery() const +{ + return query; +} + +void SqlQuery::setFlags(Db::Flags flags) +{ + this->flags = flags; +} + +void SqlQuery::clearArgs() +{ + queryArgs = QVariant(); +} + +void SqlQuery::setArgs(const QList& args) +{ + queryArgs = args; +} + +void SqlQuery::setArgs(const QHash& args) +{ + queryArgs = args; +} + + +void RowIdConditionBuilder::setRowId(const RowId& rowId) +{ + static const QString argTempalate = QStringLiteral(":rowIdArg%1"); + + QString arg; + QHashIterator it(rowId); + int i = 0; + while (it.hasNext()) + { + it.next(); + arg = argTempalate.arg(i++); + queryArgs[arg] = it.value(); + conditions << it.key() + " = " + arg; + } +} + +const QHash& RowIdConditionBuilder::getQueryArgs() +{ + return queryArgs; +} + +QString RowIdConditionBuilder::build() +{ + return conditions.join(" AND "); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h new file mode 100644 index 0000000..a7610fa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h @@ -0,0 +1,323 @@ +#ifndef SQLQUERY_H +#define SQLQUERY_H + +#include "coreSQLiteStudio_global.h" +#include "db/db.h" +#include "db/sqlresultsrow.h" +#include +#include + +/** @file */ + +/** + * @brief Row ID of the row in any table. + * + * It's a advanced ROWID container that can hold either simple integer ROWID, + * or a set of column values reflecting one or more PRIMARY KEY columns. + * + * This way it RowId can be applied to both regular tables, as well as "WITHOUT ROWID" tables. + * + * Each entry in the RowId has a key and a value (after all it's a QHash). Keys are names + * of the column and values are values in that columns. For regular tables the RowId + * will contain exactly one entry: ROWID -> some_integer + */ +typedef QHash RowId; + +/** + * @brief SQL query to execute and get results from + * + * This object is created by and returned from Db::exec() (and familiar methods) + * or Db::prepare() calls. + * It uses incremental reading for accessing data, so it only reads as much data + * as you ask it to. It can tell you how many rows and how many columns are available + * in the results. It also provides information about errors that occured during query execution. + * + * Typical workflow looks like this: + * @code + * SqlQueryPtr results = db->exec("SELECT * FROM table"); + * SqlResultsRowPtr row; + * while (row = results->next()) + * { + * qDebug() << row->valueList(); + * } + * @endcode + * + * To re-use compiled query, use it like this: + * @code + * SqlQueryPtr query = db->prepare("SELECT * FROM table WHERE id BETWEEN ? AND ?"); + * SqlResultsRowPtr row; + * + * query->setArgs({5, 10}); + * query->execute(); + * while (row = query->next()) + * { + * qDebug() << row->valueList(); + * } + * + * query->setArgs({1, 3}); + * query->execute(); + * while (row = query->next()) + * { + * qDebug() << row->valueList(); + * } + * @endcode + */ +class API_EXPORT SqlQuery +{ + public: + /** + * @brief Releases result resources. + */ + virtual ~SqlQuery(); + + /** + * @brief Executes or re-executes prepared query. + * @return true on success, false on failure. + * + * You can ignore result of this method and check for error later with isError(). + */ + virtual bool execute(); + + /** + * @brief Reads next row of results + * @return Next results row, or null pointer if no more rows are available. + */ + SqlResultsRowPtr next(); + + /** + * @brief Tells if there is next row available. + * @return true if there's next row, of false if there's not. + * + * If you just want to iterate through rows, you don't need to call this method. + * The next() method will return null pointer if there is no next row available, + * so you can tell when to stop iterating. Furthermore, you should avoid + * calling this method just for iterating through rows, because next() method + * already does that internally (in most implementations). + * + * In other cases this method might be useful. For example when you read single cell: + * @code + * SqlQueryPtr results = db->("SELECT value FROM table WHERE rowid = ?", {rowId}); + * if (results->isError() || !results->hasNext()) + * return "some default value"; + * + * return results->getSingleCell().toString(); + * @endcode + */ + bool hasNext(); + + /** + * @brief Gets error test of the most recent error. + * @return Error text. + */ + virtual QString getErrorText() = 0; + + /** + * @brief Gets error code of the most recent error. + * @return Error code as returned from DbPlugin. + */ + virtual int getErrorCode() = 0; + + /** + * @brief Gets list of column names in the results. + * @return List of column names. + */ + virtual QStringList getColumnNames() = 0; + + /** + * @brief Gets number of columns in the results. + * @return Columns count. + */ + virtual int columnCount() = 0; + + /** + * @brief Gets number of rows that were affected by the query. + * @return Number of rows affected. + * + * For SELECT statements this is number of returned rows. + * For UPDATE this is number of rows updated. + * For DELETE this is number of rows deleted. + * FOR INSERT this is number of rows inserted (starting with SQLite 3.7.11 you can insert multiple rows with single INSERT statement). + */ + virtual qint64 rowsAffected(); + + /** + * @brief Reads all rows immediately and returns them. + * @return All data rows as a list. + * + * Don't use this method against huge data results. + */ + virtual QList getAll(); + + /** + * @brief Loads all data immediately into memory. + * + * This method makes sense only if you plan to use getAll() later on. + * If you won't use getAll(), then calling this method is just a waste of memory. + * + * It is useful if you execute query asynchronously and you will be using all results. + * In that case the asynchronous execution takes care of loading data from the database and the final code + * just operates on in-memory data. + */ + virtual void preload(); + + /** + * @brief Reads first column of first row and returns its value. + * @return Value read. + * + * This method is useful when dealing with for example PRAGMA statement results, + * or for SELECT queries with expected single row and single column. + */ + virtual QVariant getSingleCell(); + + /** + * @brief Tells if there was an error while query execution. + * @return true if there was an error, false otherwise. + */ + virtual bool isError(); + + /** + * @brief Tells if the query execution was interrupted. + * @return true if query was interrupted, or false otherwise. + * + * Interruption of execution is interpreted as an execution error, + * so if this method returns true, then isError() will return true as well. + */ + virtual bool isInterrupted(); + + /** + * @brief Retrieves ROWID of the INSERT'ed row. + * @return ROWID as 64-bit signed integer or set of multiple columns. If empty, then there was no row inserted. + * @see RowId + * @see getRegularInsertRowId() + */ + virtual RowId getInsertRowId(); + + /** + * @brief Retrieves ROWID of the INSERT'ed row. + * @return ROWID as 64-bit signed integer. + * + * This is different from getInsertRowId(), because it assumes that the insert was made to a regular table, + * while getInsertRowId() supports also inserts to WITHOUT ROWID tables. + * + * If you know that the insert was made to a regular table, you can use this method to simply get the ROWID. + */ + virtual qint64 getRegularInsertRowId(); + + /** + * @brief columnAsList + * @tparam T Data type to use for the result list. + * @param name name of the column to get values from. + * @return List of all values from given column. + */ + template + QList columnAsList(const QString& name) + { + QList list; + SqlResultsRowPtr row; + while (hasNext()) + { + row = next(); + list << row->value(name).value(); + } + return list; + } + + /** + * @brief columnAsList + * @tparam T Data type to use for the result list. + * @param index Index of the column to get values from (must be between 0 and columnCount()-1). + * @return List of all values from given column. + */ + template + QList columnAsList(int index) + { + QList list; + if (index < 0 || index >= columnCount()) + return list; + + SqlResultsRowPtr row; + while (hasNext()) + { + row = next(); + list << row->value(index).value(); + } + return list; + } + + QString getQuery() const; + void setFlags(Db::Flags flags); + void clearArgs(); + void setArgs(const QList& args); + void setArgs(const QHash& args); + + protected: + /** + * @brief Reads next row of results + * @return Next results row, or null pointer if no more rows are available. + * + * This is pretty much the same as next(), except next() handles preloaded data, + * while this method should work natively on the derived implementation of results object. + */ + virtual SqlResultsRowPtr nextInternal() = 0; + + /** + * @brief Tells if there is next row available. + * @return true if there's next row, of false if there's not. + * + * This is pretty much the same as hasNext(), except hasNext() handles preloaded data, + * while this method should work natively on the derived implementation of results object. + */ + virtual bool hasNextInternal() = 0; + + virtual bool execInternal(const QList& args) = 0; + virtual bool execInternal(const QHash& args) = 0; + + /** + * @brief Row ID of the most recently inserted row. + */ + RowId insertRowId; + + /** + * @brief Flag indicating if the data was preloaded with preload(). + */ + bool preloaded = false; + + /** + * @brief Index of the next row to be returned. + * + * If the data was preloaded (see preload()), then iterating with next() whould use this index to find out + * which preloaded row should be returned next. + */ + int preloadedRowIdx = -1; + + /** + * @brief Data preloaded with preload(). + */ + QList preloadedData; + + int affected = 0; + + QString query; + QVariant queryArgs; + Db::Flags flags; +}; + +class API_EXPORT RowIdConditionBuilder +{ + public: + void setRowId(const RowId& rowId); + const QHash& getQueryArgs(); + QString build(); + + protected: + QStringList conditions; + QHash queryArgs; +}; + +/** + * @brief Shared pointer to query object. + * Results are usually passed as shared pointer, so it's used as needed and deleted when no longer required. + */ +typedef QSharedPointer SqlQueryPtr; + +#endif // SQLQUERY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.cpp new file mode 100644 index 0000000..bb5c6e2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.cpp @@ -0,0 +1,42 @@ +#include "sqlresultsrow.h" + +SqlResultsRow::SqlResultsRow() +{ +} + +SqlResultsRow::~SqlResultsRow() +{ +} + +const QVariant SqlResultsRow::value(const QString &key) const +{ + return valuesMap[key]; +} + +const QHash &SqlResultsRow::valueMap() const +{ + return valuesMap; +} + +const QList& SqlResultsRow::valueList() const +{ + return values; +} + +const QVariant SqlResultsRow::value(int idx) const +{ + if (idx < 0 || idx >= values.size()) + return QVariant(); + + return values[idx]; +} + +bool SqlResultsRow::contains(const QString &key) const +{ + return valuesMap.contains(key); +} + +bool SqlResultsRow::contains(int idx) const +{ + return idx >= 0 && idx < values.size(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h new file mode 100644 index 0000000..2a5e17c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h @@ -0,0 +1,102 @@ +#ifndef SQLRESULTSROW_H +#define SQLRESULTSROW_H + +#include "coreSQLiteStudio_global.h" +#include +#include +#include +#include + +/** @file */ + +/** + * @brief SQL query results row. + * + * Single row of data from SQL query results. It already has all columns stored in memory, + * so it doesn't matter if you read only one column, or all columns available in the row. + * + * You will never encounter object of exactly this class, as it has protected constructor + * and has no methods to populate internal data members. Instead of creating objects of this class, + * other class inherits it and handles populating internal data members, then this class + * is just an interface to read data from it. + * + * In other words, it's kind of an abstract class. + */ +class API_EXPORT SqlResultsRow +{ + public: + /** + * @brief Releases resources. + */ + virtual ~SqlResultsRow(); + + /** + * @brief Gets value for given column. + * @param key Column name. + * @return Value from requested column. If column name is invalid, the invalid QVariant is returned. + */ + const QVariant value(const QString& key) const; + + /** + * @brief Gets value for given column. + * @param idx 0-based index of column. + * @return Value from requested column. If index was invalid, the invalid QVariant is returned. + */ + const QVariant value(int idx) const; + + /** + * @brief Gets table of column->value entries. + * @return Hash table with column names as keys and QVariants as their values. + * + * Note, that QHash doesn't guarantee order of entries. If you want to iterate through columns + * in order they were returned from the database, use valueList(), or iterate through SqlResults::getColumnNames() + * and use it to call value(). + */ + const QHash& valueMap() const; + + /** + * @brief Gets list of values in this row. + * @return Ordered list of values in the row. + * + * Note, that this method returns values in order they were returned from database. + */ + const QList& valueList() const; + + /** + * @brief Tests if the row contains given column name. + * @param key Column name. Case sensitive. + * @return true if column exists in the row, or false otherwise. + */ + bool contains(const QString& key) const; + + /** + * @brief Tests if the row has column indexed with given number. + * @param idx 0-based index to test. + * @return true if index is in range of existing columns, or false if it's greater than "number of columns - 1", or if it's less than 0. + */ + bool contains(int idx) const; + + protected: + SqlResultsRow(); + + /** + * @brief Columns and their values in the row. + */ + QHash valuesMap; + /** + * @brief Ordered list of values in the row. + * + * Technical note: + * We keep list of values next to valuesMap, so we have it in the same order as column names when asked by valueList(). + * This looks like having redundant data storage, but Qt container classes (such as QVariant) + * use smart pointers to keep their data internally, so here we actually keep only reference objects. + */ + QList values; +}; + +/** + * @brief Shared pointer to SQL query results row. + */ +typedef QSharedPointer SqlResultsRowPtr; + +#endif // SQLRESULTSROW_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h new file mode 100644 index 0000000..eff3621 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h @@ -0,0 +1,83 @@ +#ifndef STDSQLITE3DRIVER_H +#define STDSQLITE3DRIVER_H + +#define STD_SQLITE3_DRIVER(Name, Label, Prefix, UppercasePrefix) \ + struct Name \ + { \ + static_char* label = Label; \ + \ + static const int OK = UppercasePrefix##SQLITE_OK; \ + static const int ERROR = UppercasePrefix##SQLITE_ERROR; \ + static const int OPEN_READWRITE = UppercasePrefix##SQLITE_OPEN_READWRITE; \ + static const int OPEN_CREATE = UppercasePrefix##SQLITE_OPEN_CREATE; \ + static const int UTF8 = UppercasePrefix##SQLITE_UTF8; \ + static const int INTEGER = UppercasePrefix##SQLITE_INTEGER; \ + static const int FLOAT = UppercasePrefix##SQLITE_FLOAT; \ + static const int NULL_TYPE = UppercasePrefix##SQLITE_NULL; \ + static const int BLOB = UppercasePrefix##SQLITE_BLOB; \ + static const int MISUSE = UppercasePrefix##SQLITE_MISUSE; \ + static const int BUSY = UppercasePrefix##SQLITE_BUSY; \ + static const int ROW = UppercasePrefix##SQLITE_ROW; \ + static const int DONE = UppercasePrefix##SQLITE_DONE; \ + \ + typedef Prefix##sqlite3 handle; \ + typedef Prefix##sqlite3_stmt stmt; \ + typedef Prefix##sqlite3_context context; \ + typedef Prefix##sqlite3_value value; \ + typedef Prefix##sqlite3_int64 int64; \ + typedef Prefix##sqlite3_destructor_type destructor_type; \ + \ + static destructor_type TRANSIENT() {return UppercasePrefix##SQLITE_TRANSIENT;} \ + static void interrupt(handle* arg) {Prefix##sqlite3_interrupt(arg);} \ + static const void *value_blob(value* arg) {return Prefix##sqlite3_value_blob(arg);} \ + static double value_double(value* arg) {return Prefix##sqlite3_value_double(arg);} \ + static int64 value_int64(value* arg) {return Prefix##sqlite3_value_int64(arg);} \ + static const void *value_text16(value* arg) {return Prefix##sqlite3_value_text16(arg);} \ + static int value_bytes(value* arg) {return Prefix##sqlite3_value_bytes(arg);} \ + static int value_bytes16(value* arg) {return Prefix##sqlite3_value_bytes16(arg);} \ + static int value_type(value* arg) {return Prefix##sqlite3_value_type(arg);} \ + static int bind_blob(stmt* a1, int a2, const void* a3, int a4, void(*a5)(void*)) {return Prefix##sqlite3_bind_blob(a1, a2, a3, a4, a5);} \ + static int bind_double(stmt* a1, int a2, double a3) {return Prefix##sqlite3_bind_double(a1, a2, a3);} \ + static int bind_int(stmt* a1, int a2, int a3) {return Prefix##sqlite3_bind_int(a1, a2, a3);} \ + static int bind_int64(stmt* a1, int a2, int64 a3) {return Prefix##sqlite3_bind_int64(a1, a2, a3);} \ + static int bind_null(stmt* a1, int a2) {return Prefix##sqlite3_bind_null(a1, a2);} \ + static int bind_text16(stmt* a1, int a2, const void* a3, int a4, void(*a5)(void*)) {return Prefix##sqlite3_bind_text16(a1, a2, a3, a4, a5);} \ + static void result_blob(context* a1, const void* a2, int a3, void(*a4)(void*)) {Prefix##sqlite3_result_blob(a1, a2, a3, a4);} \ + static void result_double(context* a1, double a2) {Prefix##sqlite3_result_double(a1, a2);} \ + static void result_error16(context* a1, const void* a2, int a3) {Prefix##sqlite3_result_error16(a1, a2, a3);} \ + static void result_int(context* a1, int a2) {Prefix##sqlite3_result_int(a1, a2);} \ + static void result_int64(context* a1, int64 a2) {Prefix##sqlite3_result_int64(a1, a2);} \ + static void result_null(context* a1) {Prefix##sqlite3_result_null(a1);} \ + static void result_text16(context* a1, const void* a2, int a3, void(*a4)(void*)) {Prefix##sqlite3_result_text16(a1, a2, a3, a4);} \ + static int open_v2(const char *a1, handle **a2, int a3, const char *a4) {return Prefix##sqlite3_open_v2(a1, a2, a3, a4);} \ + static int finalize(stmt *arg) {return Prefix##sqlite3_finalize(arg);} \ + static const char *errmsg(handle* arg) {return Prefix##sqlite3_errmsg(arg);} \ + static int extended_errcode(handle* arg) {return Prefix##sqlite3_extended_errcode(arg);} \ + static const void *column_blob(stmt* arg1, int arg2) {return Prefix##sqlite3_column_blob(arg1, arg2);} \ + static int column_bytes(stmt* arg1, int arg2) {return Prefix##sqlite3_column_bytes(arg1, arg2);} \ + static int column_bytes16(stmt* arg1, int arg2) {return Prefix##sqlite3_column_bytes16(arg1, arg2);} \ + static double column_double(stmt* arg1, int arg2) {return Prefix##sqlite3_column_double(arg1, arg2);} \ + static int64 column_int64(stmt* arg1, int arg2) {return Prefix##sqlite3_column_int64(arg1, arg2);} \ + static const void *column_text16(stmt* arg1, int arg2) {return Prefix##sqlite3_column_text16(arg1, arg2);} \ + static const char *column_name(stmt* arg1, int arg2) {return Prefix##sqlite3_column_name(arg1, arg2);} \ + static int column_type(stmt* arg1, int arg2) {return Prefix##sqlite3_column_type(arg1, arg2);} \ + static int column_count(stmt* arg1) {return Prefix##sqlite3_column_count(arg1);} \ + static int changes(handle* arg) {return Prefix##sqlite3_changes(arg);} \ + static int last_insert_rowid(handle* arg) {return Prefix##sqlite3_last_insert_rowid(arg);} \ + static int step(stmt* arg) {return Prefix##sqlite3_step(arg);} \ + static int reset(stmt* arg) {return Prefix##sqlite3_reset(arg);} \ + static int close(handle* arg) {return Prefix##sqlite3_close(arg);} \ + static int enable_load_extension(handle* arg1, int arg2) {return Prefix##sqlite3_enable_load_extension(arg1, arg2);} \ + static void* user_data(context* arg) {return Prefix##sqlite3_user_data(arg);} \ + static void* aggregate_context(context* arg1, int arg2) {return Prefix##sqlite3_aggregate_context(arg1, arg2);} \ + static int collation_needed(handle* a1, void* a2, void(*a3)(void*,handle*,int eTextRep,const char*)) {return Prefix##sqlite3_collation_needed(a1, a2, a3);} \ + static int prepare_v2(handle *a1, const char *a2, int a3, stmt **a4, const char **a5) {return Prefix##sqlite3_prepare_v2(a1, a2, a3, a4, a5);} \ + static int create_function(handle *a1, const char *a2, int a3, int a4, void *a5, void (*a6)(context*,int,value**), void (*a7)(context*,int,value**), void (*a8)(context*)) \ + {return Prefix##sqlite3_create_function(a1, a2, a3, a4, a5, a6, a7, a8);} \ + static int create_function_v2(handle *a1, const char *a2, int a3, int a4, void *a5, void (*a6)(context*,int,value**), void (*a7)(context*,int,value**), void (*a8)(context*), void(*a9)(void*)) \ + {return Prefix##sqlite3_create_function_v2(a1, a2, a3, a4, a5, a6, a7, a8, a9);} \ + static int create_collation_v2(handle* a1, const char *a2, int a3, void *a4, int(*a5)(void*,int,const void*,int,const void*), void(*a6)(void*)) \ + {return Prefix##sqlite3_create_collation_v2(a1, a2, a3, a4, a5, a6);} \ + }; + +#endif // STDSQLITE3DRIVER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp b/SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp new file mode 100644 index 0000000..382f515 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp @@ -0,0 +1,11 @@ +#include "dbattacher.h" +#include "impl/dbattacherimpl.h" + +DbAttacher::~DbAttacher() +{ +} + +DbAttacherFactory::~DbAttacherFactory() +{ +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/dbattacher.h b/SQLiteStudio3/coreSQLiteStudio/dbattacher.h new file mode 100644 index 0000000..5b94832 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbattacher.h @@ -0,0 +1,99 @@ +#ifndef DBATTACHER_H +#define DBATTACHER_H + +#include "parser/ast/sqlitequery.h" +#include "common/bistrhash.h" +#include "common/strhash.h" +#include + +class Db; + +/** + * @brief Transparent attaching of databases used in the query. + * + * Scans query (or queries) for names of databases registered in DbManager. + * If a database is recognized, it's automatically attached and the mapping + * of its database name to its attach name is created for later information. + * + * The attacher looks for database names only at those spots in the query, + * where the database name is valid by SQLite syntax (this is accomplished + * with SqliteStatement::getContextDatabaseTokens()). + */ +class API_EXPORT DbAttacher +{ + public: + /** + * @brief Default destructor. + */ + virtual ~DbAttacher(); + + /** + * @brief Scans for databases in given query and attaches them. + * @param query Query string to be executed. + * @return true on success, or false on failure. + * + * The method can fail if any of databases used in the query could + * not be attached (the ATTACH statement caused error). + * + * To get query with database names replaced with attach names use getQuery(). + */ + virtual bool attachDatabases(const QString& query) = 0; + + /** + * Be aware that database names in queries are replaced with attach names in SqliteStatement::tokens, + * thus modified query can be achived with SqliteStatement::detokenize(). This also means + * that the input queries will contain modified token list. + * + * @overload + */ + virtual bool attachDatabases(const QList& queries) = 0; + + /** + * @overload + */ + virtual bool attachDatabases(SqliteQueryPtr query) = 0; + + /** + * @brief Detaches all databases attached by the attacher. + */ + virtual void detachDatabases() = 0; + + /** + * @brief Provides mapping of database names to their attach names. + * @return Database name to attach name mapping. + * + * The returned map is bi-directional, so you can easly translate database name to attach name + * and vice versa. Left values of the map are database names (as registered in DbManager) + * and right values are attach names assigned to them. + */ + virtual BiStrHash getDbNameToAttach() const = 0; + + /** + * @brief Provides query string updated with attach names. + * @return Query string. + */ + virtual QString getQuery() const = 0; +}; + +/** + * @brief Abstract factory for DbAttacher objects. + * + * The abstract factory is accessed from SQLiteStudio class in order to produce DbAttacher instances. + * The default DbAttacherFactory implementation (DbAttacherFactoryImpl) produces default DbAttacher instances + * (DbAttacherImpl), but it can be replaced with other factory to produce other attachers, just like unit tests + * in this project do. + */ +class API_EXPORT DbAttacherFactory +{ + public: + virtual ~DbAttacherFactory(); + + /** + * @brief Produces single attacher instance. + * @param db Database to produce attacher for. + * @return Attacher instance. Factory doesn't own it, you have to delete it when you're done. + */ + virtual DbAttacher* create(Db* db) = 0; +}; + +#endif // DBATTACHER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.cpp b/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.cpp new file mode 100644 index 0000000..61f5cb2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.cpp @@ -0,0 +1,777 @@ +#include "dbobjectorganizer.h" +#include "db/db.h" +#include "common/utils_sql.h" +#include "datatype.h" +#include "services/notifymanager.h" +#include "db/attachguard.h" +#include "dbversionconverter.h" +#include +#include + +DbObjectOrganizer::DbObjectOrganizer() +{ + // Default organizaer denies any referenced objects + confirmFunction = [](const QStringList&) -> bool {return false;}; + nameConflictResolveFunction = [](QString&) -> bool {return false;}; + conversionConfimFunction = [](const QList>&) -> bool {return false;}; + conversionErrorsConfimFunction = [](const QHash>&) -> bool {return false;}; + init(); +} + +DbObjectOrganizer::DbObjectOrganizer(DbObjectOrganizer::ReferencedTablesConfimFunction confirmFunction, NameConflictResolveFunction nameConflictResolveFunction, + ConversionConfimFunction conversionConfimFunction, ConversionErrorsConfimFunction conversionErrorsConfimFunction) : + confirmFunction(confirmFunction), nameConflictResolveFunction(nameConflictResolveFunction), conversionConfimFunction(conversionConfimFunction), + conversionErrorsConfimFunction(conversionErrorsConfimFunction) +{ + init(); +} + +DbObjectOrganizer::~DbObjectOrganizer() +{ + safe_delete(srcResolver); + safe_delete(dstResolver); + safe_delete(versionConverter); +} + +void DbObjectOrganizer::init() +{ + versionConverter = new DbVersionConverter(); + connect(this, SIGNAL(preparetionFinished()), this, SLOT(processPreparationFinished())); +} + +void DbObjectOrganizer::copyObjectsToDb(Db* srcDb, const QStringList& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers) +{ + copyOrMoveObjectsToDb(srcDb, objNames.toSet(), dstDb, includeData, includeIndexes, includeTriggers, false); +} + +void DbObjectOrganizer::moveObjectsToDb(Db* srcDb, const QStringList& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers) +{ + copyOrMoveObjectsToDb(srcDb, objNames.toSet(), dstDb, includeData, includeIndexes, includeTriggers, true); +} + +void DbObjectOrganizer::interrupt() +{ + QMutexLocker locker(&interruptMutex); + interrupted = true; + srcDb->interrupt(); + dstDb->interrupt(); +} + +bool DbObjectOrganizer::isExecuting() +{ + QMutexLocker lock(&executingMutex); + return executing; +} + +void DbObjectOrganizer::run() +{ + switch (mode) + { + case Mode::PREPARE_TO_COPY_OBJECTS: + case Mode::PREPARE_TO_MOVE_OBJECTS: + processPreparation(); + break; + case Mode::COPY_OBJECTS: + case Mode::MOVE_OBJECTS: + emitFinished(processAll()); + break; + case Mode::unknown: + qCritical() << "DbObjectOrganizer::run() called with unknown mode."; + emitFinished(false); + return; + } +} + +void DbObjectOrganizer::reset() +{ + attachName = QString(); + mode = Mode::COPY_OBJECTS; + srcDb = nullptr; + dstDb = nullptr; + srcNames.clear(); + srcTables.clear(); + srcIndexes.clear(); + srcTriggers.clear(); + srcViews.clear(); + renamed.clear(); + srcTable = QString::null; + includeData = false; + includeIndexes = false; + includeTriggers = false; + deleteSourceObjects = false; + referencedTables.clear(); + diffListToConfirm.clear(); + errorsToConfirm.clear(); + binaryColumns.clear(); + safe_delete(srcResolver); + safe_delete(dstResolver); + interrupted = false; + setExecuting(false); +} + +void DbObjectOrganizer::copyOrMoveObjectsToDb(Db* srcDb, const QSet& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers, bool move) +{ + if (isExecuting()) + { + notifyError("Schema modification is currently in progress. Please try again in a moment."); + qWarning() << "Tried to call DbObjectOrganizer::copyOrMoveObjectsToDb() while other execution was in progress."; + return; + } + + reset(); + setExecuting(true); + if (move) + { + mode = Mode::PREPARE_TO_MOVE_OBJECTS; + deleteSourceObjects = true; + } + else + { + mode = Mode::PREPARE_TO_COPY_OBJECTS; + } + + this->srcNames = objNames; + this->includeData = includeData; + this->includeIndexes = includeIndexes; + this->includeTriggers = includeTriggers; + setSrcAndDstDb(srcDb, dstDb); + + QThreadPool::globalInstance()->start(this); +} + +void DbObjectOrganizer::processPreparation() +{ + StrHash allParsedObjects = srcResolver->getAllParsedObjects(); + StrHash details = srcResolver->getAllObjectDetails(); + for (const QString& srcName : srcNames) + { + if (!details.contains(srcName)) + { + qDebug() << "Object" << srcName << "not found in source database, skipping."; + continue; + } + + switch (details[srcName].type) + { + case SchemaResolver::TABLE: + srcTables << srcName; + findBinaryColumns(srcName, allParsedObjects); + collectReferencedTables(srcName, allParsedObjects); + collectReferencedIndexes(srcName); + collectReferencedTriggersForTable(srcName); + break; + case SchemaResolver::INDEX: + break; + case SchemaResolver::TRIGGER: + break; + case SchemaResolver::VIEW: + srcViews << srcName; + collectReferencedTriggersForView(srcName); + break; + case SchemaResolver::ANY: + qCritical() << "Unhandled type in DbObjectOrganizer::processPreparation():" << SchemaResolver::objectTypeToString(details[srcName].type); + break; + } + } + + if (referencedTables.size() > 0 && !confirmFunction(referencedTables.toList())) + referencedTables.clear(); + + for (const QString& srcTable : referencedTables) + { + collectReferencedIndexes(srcTable); + collectReferencedTriggersForTable(srcTable); + } + + collectDiffs(details); + + emit preparetionFinished(); +} + +bool DbObjectOrganizer::processAll() +{ + if (!srcDb->isOpen()) + { + //notifyError(tr("Cannot copy or move objects from closed database. Open it first.")); // TODO this is in another thread - handle it + return false; + } + + if (!dstDb->isOpen()) + { + //notifyError(tr("Cannot copy or move objects to closed database. Open it first.")); // TODO this is in another thread - handle it + return false; + } + + // Attaching target db if needed + AttachGuard attach; + if (!(referencedTables + srcTables).isEmpty()) + { + attach = srcDb->guardedAttach(dstDb, true); + attachName = attach->getName(); + } + + if (!srcDb->begin()) + { + // TODO message + return false; + } + + if (!dstDb->begin()) + { + // TODO message + srcDb->rollback(); + return false; + } + + if (!setFkEnabled(false)) + { + srcDb->rollback(); + dstDb->rollback(); + return false; + } + + bool res = false; + switch (mode) + { + case Mode::COPY_OBJECTS: + case Mode::MOVE_OBJECTS: + { + res = processDbObjects(); + break; + } + case Mode::PREPARE_TO_COPY_OBJECTS: + case Mode::PREPARE_TO_MOVE_OBJECTS: + { + qCritical() << "DbObjectOrganizer::processAll() called with PREAPRE mode."; + return false; // this method should not be called with this mode + } + case Mode::unknown: + { + qWarning() << "Unhandled unknown mode in DbObjectOrganizer."; + return false; + } + } + + if (!res) + { + srcDb->rollback(); + dstDb->rollback(); + setFkEnabled(true); + return false; + } + + if (!setFkEnabled(true)) + { + srcDb->rollback(); + dstDb->rollback(); + return false; + } + + if (!dstDb->commit()) + { + // notifyError(tr("Could not commit transaction in database '%1'.").arg(dstDb->getName())); // TODO this is in another thread, cannot use notifyError + dstDb->rollback(); + srcDb->rollback(); + return false; + } + + if (!srcDb->commit()) + { + // TODO message - this can happen also for attached db operations, so also for creating objects in dstDb, so this affects not only srcDb, but also dstDb + srcDb->rollback(); + return false; + } + + return true; +} + +bool DbObjectOrganizer::processDbObjects() +{ + for (const QString& table : (referencedTables + srcTables)) + { + if (!copyTableToDb(table) || isInterrupted()) + return false; + } + + for (const QString& view : srcViews) + { + if (!copyViewToDb(view) || isInterrupted()) + return false; + } + + if (includeIndexes) + { + for (const QString& idx : srcIndexes) + { + if (!copyIndexToDb(idx) || isInterrupted()) + return false; + } + } + + if (includeTriggers) + { + for (const QString& trig : srcTriggers) + { + if (!copyTriggerToDb(trig) || isInterrupted()) + return false; + } + } + + if (deleteSourceObjects) + { + for (const QString& table : (referencedTables + srcTables)) + dropTable(table); + + for (const QString& view : srcViews) + dropView(view); + } + + return true; +} + +bool DbObjectOrganizer::resolveNameConflicts() +{ + QSet names; + QStringList namesInDst; + switch (mode) + { + case Mode::PREPARE_TO_COPY_OBJECTS: + case Mode::PREPARE_TO_MOVE_OBJECTS: + case Mode::COPY_OBJECTS: + case Mode::MOVE_OBJECTS: + { + names = referencedTables + srcTables + srcViews + srcIndexes + srcTriggers; + namesInDst = dstResolver->getAllObjects(); + break; + } + case Mode::unknown: + { + qWarning() << "Unhandled unknown mode in DbObjectOrganizer::resolveNameConflicts()."; + return false; + } + } + + QString finalName; + for (const QString& srcName : names) + { + finalName = srcName; + while (namesInDst.contains(finalName, Qt::CaseInsensitive)) + { + if (!nameConflictResolveFunction(finalName)) + return false; + } + if (finalName != srcName) + renamed[srcName] = finalName; + } + return true; +} + +bool DbObjectOrganizer::copyTableToDb(const QString& table) +{ + QString ddl; + QString targetTable = table; +// AttachGuard attach = srcDb->guardedAttach(dstDb, true); +// QString attachName = attach->getName(); + if (renamed.contains(table) || !attachName.isNull()) + { + SqliteQueryPtr parsedObject = srcResolver->getParsedObject(table, SchemaResolver::TABLE); + SqliteCreateTablePtr createTable = parsedObject.dynamicCast(); + if (!createTable) + { + qCritical() << "Could not parse table while copying:" << table << ", ddl:" << srcResolver->getObjectDdl(table, SchemaResolver::TABLE); + notifyError(tr("Error while creating table in target database: %1").arg(tr("Could not parse table."))); + return false; + } + + if (renamed.contains(table)) + targetTable = renamed[table]; + + createTable->table = targetTable; + if (!attachName.isNull()) + createTable->database = attachName; + + createTable->rebuildTokens(); + ddl = createTable->detokenize(); + } + else + { + ddl = srcResolver->getObjectDdl(table, SchemaResolver::TABLE); + } + + ddl = convertDdlToDstVersion(ddl); + if (ddl.trimmed() == ";") // empty query, result of ignored errors in UI + return true; + + SqlQueryPtr result; + + if (attachName.isNull()) + result = dstDb->exec(ddl); + else + result = srcDb->exec(ddl); // uses attachName to create object in attached db + + if (result->isError()) + { + notifyError(tr("Error while creating table in target database: %1").arg(result->getErrorText())); + return false; + } + + if (!includeData) + return true; + + if (isInterrupted()) + return false; + + srcTable = table; + bool res; + if (attachName.isNull()) + { + notifyInfo(tr("Database %1 could not be attached to database %2, so the data of table %3 will be copied " + "with SQLiteStudio as a mediator. This method can be slow for huge tables, so please be patient.") + .arg(dstDb->getName(), srcDb->getName(), srcTable)); + + res = copyDataAsMiddleware(targetTable); + } + else + { + res = copyDataUsingAttach(targetTable); + } + return res; +} + +bool DbObjectOrganizer::copyDataAsMiddleware(const QString& table) +{ + QStringList srcColumns = srcResolver->getTableColumns(srcTable); + QString wrappedSrcTable = wrapObjIfNeeded(srcTable, srcDb->getDialect()); + SqlQueryPtr results = srcDb->prepare("SELECT * FROM " + wrappedSrcTable); + setupSqlite2Helper(results, table, srcColumns); + if (!results->execute()) + { + notifyError(tr("Error while copying data for table %1: %2").arg(table).arg(results->getErrorText())); + return false; + } + + QStringList argPlaceholderList; + for (int i = 0, total = srcColumns.size(); i < total; ++i) + argPlaceholderList << "?"; + + QString wrappedDstTable = wrapObjIfNeeded(table, dstDb->getDialect()); + QString sql = "INSERT INTO " + wrappedDstTable + " VALUES (" + argPlaceholderList.join(", ") + ")"; + SqlQueryPtr insertQuery = dstDb->prepare(sql); + + SqlResultsRowPtr row; + int i = 0; + while (results->hasNext()) + { + row = results->next(); + if (!row) + { + notifyError(tr("Error while copying data to table %1: %2").arg(table).arg(results->getErrorText())); + return false; + } + + insertQuery->setArgs(row->valueList()); + if (!insertQuery->execute()) + { + notifyError(tr("Error while copying data to table %1: %2").arg(table).arg(insertQuery->getErrorText())); + return false; + } + + if ((i % 1000) == 0 && isInterrupted()) + return false; + + i++; + } + + if (isInterrupted()) + return false; + + return true; +} + +bool DbObjectOrganizer::copyDataUsingAttach(const QString& table) +{ + QString wrappedSrcTable = wrapObjIfNeeded(srcTable, srcDb->getDialect()); + QString wrappedDstTable = wrapObjIfNeeded(table, srcDb->getDialect()); + SqlQueryPtr results = srcDb->exec("INSERT INTO " + attachName + "." + wrappedDstTable + " SELECT * FROM " + wrappedSrcTable); + if (results->isError()) + { + notifyError(tr("Error while copying data to table %1: %2").arg(table).arg(results->getErrorText())); + return false; + } + return true; +} + +void DbObjectOrganizer::setupSqlite2Helper(SqlQueryPtr query, const QString& table, const QStringList& colNames) +{ + Sqlite2ColumnDataTypeHelper* sqlite2Helper = dynamic_cast(query.data()); + if (sqlite2Helper && binaryColumns.contains(table)) + { + int i = 0; + QStringList binCols = binaryColumns[table]; + for (const QString& colName : colNames) + { + if (binCols.contains(colName)) + sqlite2Helper->setBinaryType(i); + + i++; + } + } +} + +void DbObjectOrganizer::dropTable(const QString& table) +{ + dropObject(table, "TABLE"); +} + +void DbObjectOrganizer::dropView(const QString& view) +{ + dropObject(view, "VIEW"); +} + +void DbObjectOrganizer::dropObject(const QString& name, const QString& type) +{ + QString wrappedSrcObj = wrapObjIfNeeded(name, srcDb->getDialect()); + SqlQueryPtr results = srcDb->exec("DROP " + type + " " + wrappedSrcObj); + if (results->isError()) + { + notifyWarn(tr("Error while dropping source view %1: %2\nTables, indexes, triggers and views copied to database %3 will remain.") + .arg(name).arg(results->getErrorText()).arg(dstDb->getName())); + } +} + +bool DbObjectOrganizer::copyViewToDb(const QString& view) +{ + return copySimpleObjectToDb(view, tr("Error while creating view in target database: %1")); +} + +bool DbObjectOrganizer::copyIndexToDb(const QString& index) +{ + return copySimpleObjectToDb(index, tr("Error while creating index in target database: %1")); +} + +bool DbObjectOrganizer::copyTriggerToDb(const QString& trigger) +{ + return copySimpleObjectToDb(trigger, tr("Error while creating trigger in target database: %1")); +} + +bool DbObjectOrganizer::copySimpleObjectToDb(const QString& name, const QString& errorMessage) +{ + QString ddl = srcResolver->getObjectDdl(name, SchemaResolver::ANY); + QString convertedDdl = convertDdlToDstVersion(ddl); + if (convertedDdl.trimmed() == ";") // empty query, result of ignored errors in UI + return true; + + SqlQueryPtr result = dstDb->exec(convertedDdl); + if (result->isError()) + { + notifyError(errorMessage.arg(result->getErrorText())); + qDebug() << "DDL that caused error in DbObjectOrganizer::copySimpleObjectToDb():" << ddl << "\nAfter converting:" << convertedDdl; + return false; + } + + return true; +} + +QSet DbObjectOrganizer::resolveReferencedTables(const QString& table, const QList& parsedTables) +{ + QSet tables = SchemaResolver::getFkReferencingTables(table, parsedTables).toSet(); + for (const QString& fkTable : tables) + tables += SchemaResolver::getFkReferencingTables(fkTable, parsedTables).toSet(); + + tables.remove(table); // if it appeared somewhere in the references - we still don't need it here, it's the table we asked by in the first place + return tables; +} + +void DbObjectOrganizer::collectDiffs(const StrHash& details) +{ + if (srcDb->getVersion() == dstDb->getVersion()) + return; + + + int dstVersion = dstDb->getVersion(); + QSet names = srcTables + srcViews + referencedTables + srcIndexes + srcTriggers; + for (const QString& name : names) + { + if (!details.contains(name)) + { + qCritical() << "Object named" << name << "not found in details when trying to prepare Diff for copying or moving object."; + continue; + } + + versionConverter->reset(); + if (dstVersion == 3) + versionConverter->convert2To3(details[name].ddl); + else + versionConverter->convert3To2(details[name].ddl); + + diffListToConfirm += versionConverter->getDiffList(); + if (!versionConverter->getErrors().isEmpty()) + errorsToConfirm[name] = versionConverter->getErrors(); + } +} + +QString DbObjectOrganizer::convertDdlToDstVersion(const QString& ddl) +{ + if (srcDb->getVersion() == dstDb->getVersion()) + return ddl; + + if (dstDb->getVersion() == 3) + return versionConverter->convert2To3(ddl); + else + return versionConverter->convert3To2(ddl); +} + +void DbObjectOrganizer::collectReferencedTables(const QString& table, const StrHash& allParsedObjects) +{ + QList parsedTables; + SqliteCreateTablePtr parsedTable; + for (SqliteQueryPtr query : allParsedObjects.values()) + { + parsedTable = query.dynamicCast(); + if (parsedTable) + parsedTables << parsedTable; + } + + QSet tables = resolveReferencedTables(table, parsedTables); + for (const QString& refTable : tables) + { + if (!referencedTables.contains(refTable) && !srcTables.contains(refTable)) + referencedTables << refTable; + } +} + +void DbObjectOrganizer::collectReferencedIndexes(const QString& table) +{ + srcIndexes += srcResolver->getIndexesForTable(table).toSet(); +} + +void DbObjectOrganizer::collectReferencedTriggersForTable(const QString& table) +{ + srcTriggers += srcResolver->getTriggersForTable(table).toSet(); +} + +void DbObjectOrganizer::collectReferencedTriggersForView(const QString& view) +{ + srcTriggers += srcResolver->getTriggersForView(view).toSet(); +} + +void DbObjectOrganizer::findBinaryColumns(const QString& table, const StrHash& allParsedObjects) +{ + if (!allParsedObjects.contains(table)) + { + qWarning() << "Parsed objects don't have table" << table << "in DbObjectOrganizer::findBinaryColumns()"; + return; + } + + SqliteQueryPtr query = allParsedObjects[table]; + SqliteCreateTablePtr createTable = query.dynamicCast(); + if (!createTable) + { + qWarning() << "Not CreateTable in DbObjectOrganizer::findBinaryColumns()"; + return; + } + + for (SqliteCreateTable::Column* column : createTable->columns) + { + if (!column->type) + continue; + + if (DataType::isBinary(column->type->name)) + binaryColumns[table] << column->name; + } +} + +bool DbObjectOrganizer::setFkEnabled(bool enabled) +{ + if (dstDb->getVersion() == 2) + return true; + + SqlQueryPtr result = dstDb->exec(QString("PRAGMA foreign_keys = %1").arg(enabled ? "on" : "off")); + if (result->isError()) + { + // notifyError(tr("Error while executing PRAGMA on target database: %1").arg(result->getErrorText())); // TODO this is in another thread, cannot use notifyError + return false; + } + return true; +} + +bool DbObjectOrganizer::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} + +void DbObjectOrganizer::setExecuting(bool executing) +{ + QMutexLocker lock(&executingMutex); + this->executing = executing; +} + +void DbObjectOrganizer::setSrcAndDstDb(Db* srcDb, Db* dstDb) +{ + safe_delete(srcResolver); + safe_delete(dstResolver); + this->srcDb = srcDb; + this->dstDb = dstDb; + srcResolver = new SchemaResolver(srcDb); + dstResolver = new SchemaResolver(dstDb); + srcResolver->setIgnoreSystemObjects(true); + dstResolver->setIgnoreSystemObjects(true); +} + +void DbObjectOrganizer::emitFinished(bool success) +{ + switch (mode) + { + case Mode::COPY_OBJECTS: + case Mode::PREPARE_TO_COPY_OBJECTS: + emit finishedDbObjectsCopy(success, srcDb, dstDb); + break; + case Mode::PREPARE_TO_MOVE_OBJECTS: + case Mode::MOVE_OBJECTS: + emit finishedDbObjectsMove(success, srcDb, dstDb); + break; + case Mode::unknown: + break; + } + setExecuting(false); +} + +void DbObjectOrganizer::processPreparationFinished() +{ + if (errorsToConfirm.size() > 0 && !conversionErrorsConfimFunction(errorsToConfirm)) + { + emitFinished(false); + return; + } + + if (diffListToConfirm.size() > 0 && !conversionConfimFunction(diffListToConfirm)) + { + emitFinished(false); + return; + } + + if (!resolveNameConflicts()) + { + emitFinished(false); + return; + } + + switch (mode) + { + case Mode::PREPARE_TO_COPY_OBJECTS: + mode = Mode::COPY_OBJECTS; + break; + case Mode::PREPARE_TO_MOVE_OBJECTS: + mode = Mode::MOVE_OBJECTS; + break; + case Mode::COPY_OBJECTS: + case Mode::MOVE_OBJECTS: + case Mode::unknown: + qCritical() << "DbObjectOrganizer::processPreparationFinished() called with a not PREPARE mode."; + emitFinished(false); + return; + } + + QThreadPool::globalInstance()->start(this); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h b/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h new file mode 100644 index 0000000..70b748f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h @@ -0,0 +1,152 @@ +#ifndef DBOBJECTORGANIZER_H +#define DBOBJECTORGANIZER_H + +#include "coreSQLiteStudio_global.h" +#include "interruptable.h" +#include "schemaresolver.h" +#include +#include +#include +#include +#include +#include + +class Db; +class DbVersionConverter; + +class API_EXPORT DbObjectOrganizer : public QObject, public QRunnable, public Interruptable +{ + Q_OBJECT + + public: + typedef std::function ReferencedTablesConfimFunction; + typedef std::function NameConflictResolveFunction; + typedef std::function>& diffs)> ConversionConfimFunction; + typedef std::function>& errors)> ConversionErrorsConfimFunction; + + /** + * @brief Creates organizer with default handler functions. + * + * The default handler functions are not very usable - they always return false. + * It's better to use the other constructor and pass the custom implementation of + * handler functions. + */ + DbObjectOrganizer(); + + /** + * @brief Creates organizer with handler functions defined specially for it. + * @param confirmFunction Implemented function should ask user if he wants to include referenced tables + * in the copy/move action. If it returns false, then referenced tables will be excluded. + * @param nameConflictResolveFunction Implemented function should ask user for a new name for the object + * with the name passed in the argument. The new name should be returned in the very same reference argument. + * If the function returns false, then the conflict is unresolved and copy/move action will be aborted. + * When the function returns true, it means that the user entered a new name and he wants to proceed. + * @param conversionConfimFunction This function should display changes that will be made to the SQL statements + * while executing them on the target database (because of different database versions). User can cancel the process + * (in which case, the function should return false). + * @param conversionErrorsConfimFunction This function should display critical errors that occurred when tried to + * convert SQL statements for target database (when it's of different version than source database). User + * can choose to proceed (with skipping problemating database objects), or cancel the process (in which case the function + * should return false). + * + * Use this constructor always if possible. + */ + DbObjectOrganizer(ReferencedTablesConfimFunction confirmFunction, + NameConflictResolveFunction nameConflictResolveFunction, + ConversionConfimFunction conversionConfimFunction, + ConversionErrorsConfimFunction conversionErrorsConfimFunction); + ~DbObjectOrganizer(); + + void copyObjectsToDb(Db* srcDb, const QStringList& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers); + void moveObjectsToDb(Db* srcDb, const QStringList& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers); + void interrupt(); + bool isExecuting(); + void run(); + + private: + enum class Mode + { + PREPARE_TO_COPY_OBJECTS, + PREPARE_TO_MOVE_OBJECTS, + COPY_OBJECTS, + MOVE_OBJECTS, + unknown + }; + + void init(); + void reset(); + void copyOrMoveObjectsToDb(Db* srcDb, const QSet& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers, bool move); + void processPreparation(); + bool processAll(); + bool processDbObjects(); + bool processColumns(); + bool resolveNameConflicts(); + bool copyTableToDb(const QString& table); + bool copyViewToDb(const QString& view); + bool copyIndexToDb(const QString& index); + bool copyTriggerToDb(const QString& trigger); + bool copySimpleObjectToDb(const QString& name, const QString& errorMessage); + QSet resolveReferencedTables(const QString& table, const QList& parsedTables); + void collectDiffs(const StrHash& details); + QString convertDdlToDstVersion(const QString& ddl); + void collectReferencedTables(const QString& table, const StrHash& allParsedObjects); + void collectReferencedIndexes(const QString& table); + void collectReferencedTriggersForTable(const QString& table); + void collectReferencedTriggersForView(const QString& view); + void findBinaryColumns(const QString& table, const StrHash& allParsedObjects); + bool copyDataAsMiddleware(const QString& table); + bool copyDataUsingAttach(const QString& table); + void setupSqlite2Helper(SqlQueryPtr query, const QString& table, const QStringList& colNames); + void dropTable(const QString& table); + void dropView(const QString& view); + void dropObject(const QString& name, const QString& type); + bool setFkEnabled(bool enabled); + bool isInterrupted(); + void setExecuting(bool executing); + void setSrcAndDstDb(Db* srcDb, Db* dstDb); + bool begin(); + bool commit(); + bool rollback(); + void emitFinished(bool success); + + ReferencedTablesConfimFunction confirmFunction; + NameConflictResolveFunction nameConflictResolveFunction; + ConversionConfimFunction conversionConfimFunction; + ConversionErrorsConfimFunction conversionErrorsConfimFunction; + Mode mode = Mode::COPY_OBJECTS; + Db* srcDb = nullptr; + Db* dstDb = nullptr; + QSet srcNames; + QSet srcTables; + QSet srcViews; + QSet srcIndexes; + QSet srcTriggers; + QHash renamed; + QString srcTable; + QHash binaryColumns; // hints for SQLite 2 databases + bool includeData = false; + bool includeIndexes = false; + bool includeTriggers = false; + bool deleteSourceObjects = false; + QSet referencedTables; + QHash> errorsToConfirm; + QList> diffListToConfirm; + SchemaResolver* srcResolver = nullptr; + SchemaResolver* dstResolver = nullptr; + bool interrupted = false; + bool executing = false; + DbVersionConverter* versionConverter = nullptr; + QMutex interruptMutex; + QMutex executingMutex; + QString attachName; + + private slots: + void processPreparationFinished(); + + signals: + void finishedDbObjectsMove(bool success, Db* srcDb, Db* dstDb); + void finishedDbObjectsCopy(bool success, Db* srcDb, Db* dstDb); + void preparetionFinished(); +}; + +#endif // DBOBJECTORGANIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h b/SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h new file mode 100644 index 0000000..981753e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h @@ -0,0 +1,12 @@ +#ifndef DBOBJECTTYPE_H +#define DBOBJECTTYPE_H + +enum class DbObjectType +{ + TABLE, + INDEX, + TRIGGER, + VIEW +}; + +#endif // DBOBJECTTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp new file mode 100644 index 0000000..bf13eac --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp @@ -0,0 +1,1253 @@ +#include "dbversionconverter.h" +#include "schemaresolver.h" +#include "common/global.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "services/pluginmanager.h" +#include "plugins/dbplugin.h" +#include "services/dbmanager.h" +#include +#include +#include +#include + +DbVersionConverter::DbVersionConverter() +{ + connect(this, SIGNAL(askUserForConfirmation()), this, SLOT(confirmConversion())); + connect(this, SIGNAL(conversionSuccessful()), this, SLOT(registerDbAfterSuccessfulConversion())); +} + +DbVersionConverter::~DbVersionConverter() +{ + safe_delete(fullConversionConfig); + safe_delete(resolver); +} + +void DbVersionConverter::convert(Dialect from, Dialect to, Db* srcDb, const QString& targetFile, const QString& targetName, ConversionConfimFunction confirmFunc, + ConversionErrorsConfimFunction errorsConfirmFunc) +{ + safe_delete(fullConversionConfig); + fullConversionConfig = new FullConversionConfig; + fullConversionConfig->from = from; + fullConversionConfig->to = to; + fullConversionConfig->srcDb = srcDb; + fullConversionConfig->confirmFunc = confirmFunc; + fullConversionConfig->errorsConfirmFunc = errorsConfirmFunc; + fullConversionConfig->targetFile = targetFile; + fullConversionConfig->targetName = targetName; + QtConcurrent::run(this, &DbVersionConverter::fullConvertStep1); +} + +void DbVersionConverter::convert(Dialect from, Dialect to, Db* db) +{ + if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) + convert2To3(db); + else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) + convert3To2(db); + else + qCritical() << "Unsupported db conversion combination."; +} + +void DbVersionConverter::convert3To2(Db* db) +{ + reset(); + this->db = db; + targetDialect = Dialect::Sqlite2; + convertDb(); +} + +void DbVersionConverter::convert2To3(Db* db) +{ + reset(); + this->db = db; + targetDialect = Dialect::Sqlite3; + convertDb(); +} + +QString DbVersionConverter::convert(Dialect from, Dialect to, const QString& sql) +{ + if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) + return convert2To3(sql); + else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) + return convert3To2(sql); + else + { + qCritical() << "Unsupported db conversion combination."; + return QString(); + } +} + +QString DbVersionConverter::convert3To2(const QString& sql) +{ + QStringList result; + for (SqliteQueryPtr query : parse(sql, Dialect::Sqlite3)) + result << convert3To2(query)->detokenize(); + + return result.join("\n"); +} + +QString DbVersionConverter::convert2To3(const QString& sql) +{ + QStringList result; + for (SqliteQueryPtr query : parse(sql, Dialect::Sqlite2)) + result << convert2To3(query)->detokenize(); + + return result.join("\n"); +} + +SqliteQueryPtr DbVersionConverter::convert(Dialect from, Dialect to, SqliteQueryPtr query) +{ + if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) + return convert2To3(query); + else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) + return convert3To2(query); + else + { + qCritical() << "Unsupported db conversion combination."; + return SqliteQueryPtr(); + } +} + +SqliteQueryPtr DbVersionConverter::convert3To2(SqliteQueryPtr query) +{ + SqliteQueryPtr newQuery; + switch (query->queryType) + { + case SqliteQueryType::AlterTable: + errors << QObject::tr("SQLite 2 does not support 'ALTER TABLE' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Analyze: + errors << QObject::tr("SQLite 2 does not support 'ANAYLZE' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Attach: + newQuery = copyQuery(query); + break; + case SqliteQueryType::BeginTrans: + newQuery = copyQuery(query); + break; + case SqliteQueryType::CommitTrans: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Copy: + qWarning() << "COPY query passed to DbVersionConverter::convertToVersion2(). SQLite3 query should not have COPY statement."; + newQuery = copyQuery(query); + break; + case SqliteQueryType::CreateIndex: + newQuery = copyQuery(query); + if (!modifyCreateIndexForVersion2(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CreateTable: + newQuery = copyQuery(query); + if (!modifyCreateTableForVersion2(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CreateTrigger: + newQuery = copyQuery(query); + if (!modifyCreateTriggerForVersion2(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CreateView: + newQuery = copyQuery(query); + if (!modifyCreateViewForVersion2(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CreateVirtualTable: + newQuery = copyQuery(query); + if (!modifyVirtualTableForVesion2(newQuery, newQuery.dynamicCast().data())) + { + errors << QObject::tr("SQLite 2 does not support 'CREATE VIRTUAL TABLE' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + else + { + errors << QObject::tr("SQLite 2 does not support 'CREATE VIRTUAL TABLE' statement. The regular table can be created instead if you proceed."); + } + break; + case SqliteQueryType::Delete: + newQuery = copyQuery(query); + if (!modifyDeleteForVersion2(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::Detach: + newQuery = copyQuery(query); + break; + case SqliteQueryType::DropIndex: + newQuery = copyQuery(query); + break; + case SqliteQueryType::DropTable: + newQuery = copyQuery(query); + break; + case SqliteQueryType::DropTrigger: + newQuery = copyQuery(query); + break; + case SqliteQueryType::DropView: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Insert: + newQuery = copyQuery(query); + if (!modifyInsertForVersion2(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::Pragma: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Reindex: + errors << QObject::tr("SQLite 2 does not support 'REINDEX' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Release: + errors << QObject::tr("SQLite 2 does not support 'RELEASE' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Rollback: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Savepoint: + errors << QObject::tr("SQLite 2 does not support 'SAVEPOINT' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Select: + { + newQuery = copyQuery(query); + if (!modifySelectForVersion2(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + } + case SqliteQueryType::Update: + newQuery = copyQuery(query); + if (!modifyUpdateForVersion2(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::Vacuum: + newQuery = copyQuery(query); + break; + case SqliteQueryType::UNDEFINED: + qWarning() << "UNDEFINED query type passed to DbVersionConverter::convertToVersion2()."; + newQuery = SqliteEmptyQueryPtr::create(); + break; + case SqliteQueryType::EMPTY: + newQuery = copyQuery(query); + break; + } + + if (!newQuery) + { + qCritical() << "Query type not matched in DbVersionConverter::convertToVersion2():" << static_cast(query->queryType); + return SqliteQueryPtr(); + } + + if (newQuery->queryType != SqliteQueryType::EMPTY) + { + newQuery->setSqliteDialect(Dialect::Sqlite2); + newQueries << newQuery; + } + + newQuery->rebuildTokens(); + return newQuery; +} + +SqliteQueryPtr DbVersionConverter::convert2To3(SqliteQueryPtr query) +{ + SqliteQueryPtr newQuery; + switch (query->queryType) + { + case SqliteQueryType::AlterTable: + newQuery = copyQuery(query); + qWarning() << "ALTER TABLE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have ALTER TABLE statement."; + break; + case SqliteQueryType::Analyze: + newQuery = copyQuery(query); + qWarning() << "ANALYZE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have ANALYZE statement."; + break; + case SqliteQueryType::Attach: + newQuery = copyQuery(query); + break; + case SqliteQueryType::BeginTrans: + newQuery = copyQuery(query); + if (!modifyBeginTransForVersion3(newQuery.dynamicCast().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CommitTrans: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Copy: + errors << QObject::tr("SQLite 3 does not support 'COPY' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::CreateIndex: + newQuery = copyQuery(query); + break; + case SqliteQueryType::CreateTable: + newQuery = copyQuery(query); + break; + case SqliteQueryType::CreateTrigger: + newQuery = copyQuery(query); + break; + case SqliteQueryType::CreateView: + newQuery = copyQuery(query); + break; + case SqliteQueryType::CreateVirtualTable: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Delete: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Detach: + newQuery = copyQuery(query); + break; + case SqliteQueryType::DropIndex: + newQuery = copyQuery(query); + break; + case SqliteQueryType::DropTable: + newQuery = copyQuery(query); + break; + case SqliteQueryType::DropTrigger: + newQuery = copyQuery(query); + break; + case SqliteQueryType::DropView: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Insert: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Pragma: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Reindex: + newQuery = copyQuery(query); + qWarning() << "REINDEX query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have REINDEX statement."; + break; + case SqliteQueryType::Release: + newQuery = copyQuery(query); + qWarning() << "RELEASE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have RELEASE statement."; + break; + case SqliteQueryType::Rollback: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Savepoint: + newQuery = copyQuery(query); + qWarning() << "SAVEPOINT query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have SAVEPOINT statement."; + break; + case SqliteQueryType::Select: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Update: + newQuery = copyQuery(query); + break; + case SqliteQueryType::Vacuum: + newQuery = copyQuery(query); + break; + case SqliteQueryType::UNDEFINED: + qWarning() << "UNDEFINED query type passed to DbVersionConverter::convertToVersion3()."; + case SqliteQueryType::EMPTY: + newQuery = copyQuery(query); + break; + } + + if (!newQuery) + { + qCritical() << "Query type not matched in DbVersionConverter::convertToVersion3():" << static_cast(query->queryType); + return SqliteQueryPtr(); + } + + if (newQuery->queryType != SqliteQueryType::EMPTY) + { + newQuery->setSqliteDialect(Dialect::Sqlite2); + newQueries << newQuery; + } + return newQuery; +} + +QList DbVersionConverter::parse(const QString& sql, Dialect dialect) +{ + Parser parser(dialect); + if (!parser.parse(sql)) + { + errors << QObject::tr("Could not parse statement: %1\nError details: %2").arg(sql, parser.getErrorString()); + return QList(); + } + + return parser.getQueries(); +} + +bool DbVersionConverter::modifySelectForVersion2(SqliteSelect* select) +{ + if (select->with) + { + errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("SELECT"); + return false; + } + + QString sql1 = getSqlForDiff(select); + + for (SqliteSelect::Core* core : select->coreSelects) + { + if (core->valuesMode) + core->valuesMode = false; + } + + if (!modifyAllIndexedColumnsForVersion2(select)) + return false; + + if (!modifyAllExprsForVersion2(select)) + return false; + + storeDiff(sql1, select); + return true; +} + +bool DbVersionConverter::modifyDeleteForVersion2(SqliteDelete* del) +{ + if (del->with) + { + errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("DELETE"); + return false; + } + + QString sql1 = getSqlForDiff(del); + + del->indexedBy = QString::null; + del->indexedByKw = false; + del->notIndexedKw = false; + + if (!modifyAllExprsForVersion2(del)) + return false; + + storeDiff(sql1, del); + return true; +} + +bool DbVersionConverter::modifyInsertForVersion2(SqliteInsert* insert) +{ + if (insert->with) + { + errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("INSERT"); + return false; + } + + if (insert->defaultValuesKw) + { + errors << QObject::tr("SQLite 2 does not support the 'DEFAULT VALUES' clause in the 'INSERT' clause."); + return false; + } + + if (!insert->select) + { + qCritical() << "No SELECT substatement in INSERT when converting from SQLite 3 to 2."; + return false; + } + + QString sql1 = getSqlForDiff(insert); + + // Modifying SELECT deals with "VALUES" completely. + if (!modifySelectForVersion2(insert->select)) + return false; + + if (!modifyAllExprsForVersion2(insert)) + return false; + + storeDiff(sql1, insert); + return true; +} + +bool DbVersionConverter::modifyUpdateForVersion2(SqliteUpdate* update) +{ + if (update->with) + { + errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("UPDATE"); + return false; + } + + QString sql1 = getSqlForDiff(update); + + if (!modifyAllExprsForVersion2(update)) + return false; + + update->indexedBy = QString::null; + update->indexedByKw = false; + update->notIndexedKw = false; + + storeDiff(sql1, update); + return true; +} + +bool DbVersionConverter::modifyCreateTableForVersion2(SqliteCreateTable* createTable) +{ + QString sql1 = getSqlForDiff(createTable); + + if (!createTable->database.isNull()) + createTable->database = QString::null; + + // Subselect + if (createTable->select) + { + if (!modifySelectForVersion2(createTable->select)) + return false; + } + + // Table constraints + QMutableListIterator tableConstrIt(createTable->constraints); + while (tableConstrIt.hasNext()) + { + tableConstrIt.next(); + if (tableConstrIt.value()->type == SqliteCreateTable::Constraint::PRIMARY_KEY) + tableConstrIt.value()->autoincrKw = false; + } + + // Column constraints + QMutableListIterator tableColIt(createTable->columns); + while (tableColIt.hasNext()) + { + tableColIt.next(); + QMutableListIterator tableColConstrIt(tableColIt.value()->constraints); + while (tableColConstrIt.hasNext()) + { + tableColConstrIt.next(); + switch (tableColConstrIt.value()->type) + { + case SqliteCreateTable::Column::Constraint::COLLATE: // theoretically supported by SQLite2, but it raises error from SQLite2 when used + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + { + delete tableColConstrIt.value(); + tableColConstrIt.remove(); + break; + } + case SqliteCreateTable::Column::Constraint::DEFAULT: + { + if (!tableColConstrIt.value()->ctime.isNull()) + { + delete tableColConstrIt.value(); + tableColConstrIt.remove(); + } + else + tableColConstrIt.value()->name = QString::null; + + break; + } + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + { + tableColConstrIt.value()->autoincrKw = false; + break; + } + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + { + QMutableListIterator condIt(tableColConstrIt.value()->foreignKey->conditions); + while (condIt.hasNext()) + { + condIt.next(); + if (condIt.value()->reaction == SqliteForeignKey::Condition::NO_ACTION + && condIt.value()->action != SqliteForeignKey::Condition::MATCH) // SQLite 2 has no "NO ACTION" + { + condIt.remove(); + } + } + break; + } + case SqliteCreateTable::Column::Constraint::NOT_NULL: + case SqliteCreateTable::Column::Constraint::UNIQUE: + case SqliteCreateTable::Column::Constraint::CHECK: + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + } + } + + if (!modifyAllIndexedColumnsForVersion2(createTable)) + return false; + + // WITHOUT ROWID + if (!createTable->withOutRowId.isNull()) + createTable->withOutRowId = QString::null; + + storeDiff(sql1, createTable); + return true; +} + +bool DbVersionConverter::modifyCreateTriggerForVersion2(SqliteCreateTrigger* createTrigger) +{ + QString sql = getSqlForDiff(createTrigger); + + if (!createTrigger->database.isNull()) + createTrigger->database = QString::null; + + for (SqliteQuery* query : createTrigger->queries) + { + switch (query->queryType) + { + case SqliteQueryType::Delete: + { + if (!modifyDeleteForVersion2(dynamic_cast(query))) + return false; + + break; + } + case SqliteQueryType::Update: + { + if (!modifyUpdateForVersion2(dynamic_cast(query))) + return false; + + break; + } + case SqliteQueryType::Insert: + { + if (!modifyInsertForVersion2(dynamic_cast(query))) + return false; + + break; + } + case SqliteQueryType::Select: + { + if (!modifySelectForVersion2(dynamic_cast(query))) + return false; + + break; + } + default: + qWarning() << "Unexpected query type in trigger:" << sqliteQueryTypeToString(query->queryType); + break; + } + } + + storeDiff(sql, createTrigger); + return true; +} + +bool DbVersionConverter::modifyCreateIndexForVersion2(SqliteCreateIndex* createIndex) +{ + QString sql1 = getSqlForDiff(createIndex); + + if (!createIndex->database.isNull()) + createIndex->database = QString::null; + + if (createIndex->where) + { + delete createIndex->where; + createIndex->where = nullptr; + } + + if (!modifyAllIndexedColumnsForVersion2(createIndex->indexedColumns)) + return false; + + storeDiff(sql1, createIndex); + return true; +} + +bool DbVersionConverter::modifyCreateViewForVersion2(SqliteCreateView* createView) +{ + QString sql1 = getSqlForDiff(createView); + + if (!createView->database.isNull()) + createView->database = QString::null; + + if (!modifySelectForVersion2(createView->select)) + return false; + + storeDiff(sql1, createView); + return true; +} + +bool DbVersionConverter::modifyVirtualTableForVesion2(SqliteQueryPtr& query, SqliteCreateVirtualTable* createVirtualTable) +{ + if (!resolver) + return false; + + SqliteCreateTablePtr createTable = resolver->resolveVirtualTableAsRegularTable(createVirtualTable->database, createVirtualTable->table); + if (!createTable) + return false; + + QString sql = getSqlForDiff(createVirtualTable); + storeDiff(sql, createTable.data()); + + query = createTable.dynamicCast(); + return true; +} + +bool DbVersionConverter::modifyAllExprsForVersion2(SqliteStatement* stmt) +{ + QList allExprs = stmt->getAllTypedStatements(); + for (SqliteExpr* expr : allExprs) + { + if (!modifySingleExprForVersion2(expr)) + return false; + } + return true; +} + +bool DbVersionConverter::modifySingleExprForVersion2(SqliteExpr* expr) +{ + switch (expr->mode) + { + case SqliteExpr::Mode::null: + case SqliteExpr::Mode::LITERAL_VALUE: + case SqliteExpr::Mode::BIND_PARAM: + case SqliteExpr::Mode::ID: + case SqliteExpr::Mode::UNARY_OP: + case SqliteExpr::Mode::BINARY_OP: + case SqliteExpr::Mode::FUNCTION: + case SqliteExpr::Mode::SUB_EXPR: + case SqliteExpr::Mode::LIKE: + case SqliteExpr::Mode::NULL_: + case SqliteExpr::Mode::NOTNULL: + case SqliteExpr::Mode::IS: + case SqliteExpr::Mode::BETWEEN: + case SqliteExpr::Mode::CASE: + case SqliteExpr::Mode::RAISE: + break; + case SqliteExpr::Mode::CTIME: + errors << QObject::tr("SQLite 2 does not support current date or time clauses in expressions."); + return false; + case SqliteExpr::Mode::IN: + case SqliteExpr::Mode::SUB_SELECT: + { + if (!modifySelectForVersion2(expr->select)) + return false; + + break; + } + case SqliteExpr::Mode::CAST: + errors << QObject::tr("SQLite 2 does not support 'CAST' clause in expressions."); + return false; + case SqliteExpr::Mode::EXISTS: + errors << QObject::tr("SQLite 2 does not support 'EXISTS' clause in expressions."); + return false; + case SqliteExpr::Mode::COLLATE: + { + if (dynamic_cast(expr->parentStatement())) + { + // This is the only case when SQLite2 parser puts this mode into expression, that is for sortorder + break; + } + else + { + errors << QObject::tr("SQLite 2 does not support 'COLLATE' clause in expressions."); + return false; + } + } + } + return true; +} + +bool DbVersionConverter::modifyAllIndexedColumnsForVersion2(SqliteStatement* stmt) +{ + QList columns = stmt->getAllTypedStatements(); + return modifyAllIndexedColumnsForVersion2(columns); + +} + +bool DbVersionConverter::modifyAllIndexedColumnsForVersion2(const QList columns) +{ + for (SqliteIndexedColumn* idxCol : columns) + { + if (!modifySingleIndexedColumnForVersion2(idxCol)) + return false; + } + return true; +} + +bool DbVersionConverter::modifySingleIndexedColumnForVersion2(SqliteIndexedColumn* idxCol) +{ + if (!idxCol->collate.isNull()) + idxCol->collate = QString::null; + + return true; +} + +bool DbVersionConverter::modifyBeginTransForVersion3(SqliteBeginTrans* begin) +{ + QString sql1 = getSqlForDiff(begin); + begin->onConflict = SqliteConflictAlgo::null; + storeDiff(sql1, begin); + return true; +} + +bool DbVersionConverter::modifyCreateTableForVersion3(SqliteCreateTable* createTable) +{ + QString sql1 = getSqlForDiff(createTable); + + // Column constraints + QMutableListIterator tableColIt(createTable->columns); + while (tableColIt.hasNext()) + { + tableColIt.next(); + QMutableListIterator tableColConstrIt(tableColIt.value()->constraints); + while (tableColConstrIt.hasNext()) + { + tableColConstrIt.next(); + switch (tableColConstrIt.value()->type) + { + case SqliteCreateTable::Column::Constraint::CHECK: + { + tableColConstrIt.value()->onConflict = SqliteConflictAlgo::null; + break; + } + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFAULT: + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + case SqliteCreateTable::Column::Constraint::NOT_NULL: + case SqliteCreateTable::Column::Constraint::UNIQUE: + case SqliteCreateTable::Column::Constraint::COLLATE: + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + } + } + + storeDiff(sql1, createTable); + return true; +} + +QString DbVersionConverter::getSqlForDiff(SqliteStatement* stmt) +{ + stmt->rebuildTokens(); + return stmt->detokenize(); +} + +void DbVersionConverter::storeDiff(const QString& sql1, SqliteStatement* stmt) +{ + stmt->rebuildTokens(); + QString sql2 = stmt->detokenize(); + if (sql1 != sql2) + diffList << QPair(sql1, sql2); +} + +void DbVersionConverter::storeErrorDiff(SqliteStatement* stmt) +{ + stmt->rebuildTokens(); + diffList << QPair(stmt->detokenize(), ""); +} + +void DbVersionConverter::reset() +{ + db = nullptr; + targetDialect = Dialect::Sqlite3; + diffList.clear(); + newQueries.clear(); + errors.clear(); + safe_delete(resolver); +} + +QList DbVersionConverter::getSupportedVersions() const +{ + QList versions; + for (Db* db : getAllPossibleDbInstances()) + { + versions << db->getDialect(); + delete db; + } + return versions; +} + +QStringList DbVersionConverter::getSupportedVersionNames() const +{ + QStringList versions; + for (Db* db : getAllPossibleDbInstances()) + { + versions << db->getTypeLabel(); + delete db; + } + return versions; +} + +void DbVersionConverter::fullConvertStep1() +{ + convert(fullConversionConfig->from, fullConversionConfig->to, fullConversionConfig->srcDb); + emit askUserForConfirmation(); +} + +void DbVersionConverter::fullConvertStep2() +{ + QFile outputFile(fullConversionConfig->targetFile); + if (outputFile.exists() && !outputFile.remove()) + { + emit conversionFailed(tr("Target file exists, but could not be overwritten.")); + return; + } + + Db* db = nullptr; + Db* tmpDb = nullptr; + for (DbPlugin* plugin : PLUGINS->getLoadedPlugins()) + { + tmpDb = plugin->getInstance("", ":memory:", QHash()); + if (tmpDb->initAfterCreated() && tmpDb->getDialect() == fullConversionConfig->to) + db = plugin->getInstance(fullConversionConfig->targetName, fullConversionConfig->targetFile, QHash()); + + delete tmpDb; + if (db) + break; + } + + if (!db) + { + emit conversionFailed(tr("Could not find proper database plugin to create target database.")); + return; + } + + if (checkForInterrupted(db, false)) + return; + + if (!db->open()) + { + emit conversionFailed(db->getErrorText()); + return; + } + + if (!db->begin()) + { + conversionError(db, db->getErrorText()); + return; + } + + QStringList tables; + if (!fullConvertCreateObjectsStep1(db, tables)) + return; // error handled inside + + if (checkForInterrupted(db, true)) + return; + + if (!fullConvertCopyData(db, tables)) + return; // error handled inside + + if (checkForInterrupted(db, true)) + return; + + if (!fullConvertCreateObjectsStep2(db)) + return; // error handled inside + + if (checkForInterrupted(db, true)) + return; + + if (!db->commit()) + { + conversionError(db, db->getErrorText()); + return; + } + emit conversionSuccessful(); +} + +bool DbVersionConverter::fullConvertCreateObjectsStep1(Db* db, QStringList& tables) +{ + SqlQueryPtr result; + SqliteCreateTablePtr createTable; + for (const SqliteQueryPtr& query : getConverted()) + { + // Triggers and indexes are created in step2, after data was copied, to avoid invoking triggers + // and speed up copying data. + if (query->queryType == SqliteQueryType::CreateTrigger || query->queryType == SqliteQueryType::CreateIndex) + continue; + + createTable = query.dynamicCast(); + if (!createTable.isNull()) + tables << createTable->table; + + result = db->exec(query->detokenize()); + if (result->isError()) + { + conversionError(db, result->getErrorText()); + return false; + } + } + return true; +} + +bool DbVersionConverter::fullConvertCreateObjectsStep2(Db* db) +{ + SqlQueryPtr result; + for (const SqliteQueryPtr& query : getConverted()) + { + // Creating only triggers and indexes + if (query->queryType != SqliteQueryType::CreateTrigger && query->queryType != SqliteQueryType::CreateIndex) + continue; + + result = db->exec(query->detokenize()); + if (result->isError()) + { + conversionError(db, result->getErrorText()); + return false; + } + + if (checkForInterrupted(db, true)) + return false; + } + return true; +} + +bool DbVersionConverter::fullConvertCopyData(Db* db, const QStringList& tables) +{ + static const QString selectSql = QStringLiteral("SELECT * FROM %1;"); + static const QString insertSql = QStringLiteral("INSERT INTO %1 VALUES (%2);"); + + Dialect srcDialect = fullConversionConfig->srcDb->getDialect(); + Dialect trgDialect = db->getDialect(); + QString srcTable; + QString trgTable; + SqlQueryPtr result; + SqlQueryPtr dataResult; + SqlResultsRowPtr resultsRow; + int i = 1; + for (const QString& table : tables) + { + // Select data + srcTable = wrapObjIfNeeded(table, srcDialect); + trgTable = wrapObjIfNeeded(table, trgDialect); + dataResult = fullConversionConfig->srcDb->exec(selectSql.arg(srcTable)); + if (dataResult->isError()) + { + conversionError(db, dataResult->getErrorText()); + return false; + } + + if (checkForInterrupted(db, true)) + return false; + + // Copy row by row + i = 1; + while (dataResult->hasNext()) + { + // Get row + resultsRow = dataResult->next(); + if (dataResult->isError()) + { + conversionError(db, dataResult->getErrorText()); + return false; + } + + // Insert row + result = db->exec(insertSql.arg(trgTable, generateQueryPlaceholders(resultsRow->valueList().size())), resultsRow->valueList()); + if (result->isError()) + { + conversionError(db, result->getErrorText()); + return false; + } + + if (i++ % 100 == 0 && checkForInterrupted(db, true)) + return false; + } + } + + return true; +} + +bool DbVersionConverter::checkForInterrupted(Db* db, bool rollback) +{ + if (isInterrupted()) + { + conversionInterrupted(db, rollback); + return true; + } + return false; +} + +QList DbVersionConverter::getAllPossibleDbInstances() const +{ + QList dbList; + Db* db = nullptr; + for (DbPlugin* plugin : PLUGINS->getLoadedPlugins()) + { + db = plugin->getInstance("", ":memory:", QHash()); + if (!db->initAfterCreated()) + continue; + + dbList << db; + } + return dbList; +} + +QString DbVersionConverter::generateQueryPlaceholders(int argCount) +{ + QStringList args; + for (int i = 0; i < argCount; i++) + args << "?"; + + return args.join(", "); +} + +void DbVersionConverter::sortConverted() +{ + qSort(newQueries.begin(), newQueries.end(), [](const SqliteQueryPtr& q1, const SqliteQueryPtr& q2) -> bool + { + if (!q1 || !q2) + { + if (!q1) + return false; + else + return true; + } + if (q1->queryType == q2->queryType) + return false; + + if (q1->queryType == SqliteQueryType::CreateTable) + return true; + + if (q1->queryType == SqliteQueryType::CreateView && q2->queryType != SqliteQueryType::CreateTable) + return true; + + return false; + }); +} + +void DbVersionConverter::setInterrupted(bool value) +{ + QMutexLocker locker(&interruptMutex); + interrupted = value; +} + +bool DbVersionConverter::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} + +void DbVersionConverter::conversionInterrupted(Db* db, bool rollback) +{ + emit conversionAborted(); + if (rollback) + db->rollback(); + + db->close(); + + QFile file(fullConversionConfig->targetFile); + if (file.exists()) + file.remove(); +} + +void DbVersionConverter::conversionError(Db* db, const QString& errMsg) +{ + emit conversionFailed(tr("Error while converting database: %1").arg(errMsg)); + db->rollback(); + db->close(); + + QFile file(fullConversionConfig->targetFile); + if (file.exists()) + file.remove(); +} + +void DbVersionConverter::confirmConversion() +{ + if (!errors.isEmpty() && !fullConversionConfig->errorsConfirmFunc(errors)) + { + emit conversionAborted(); + return; + } + + if (!diffList.isEmpty() && !fullConversionConfig->confirmFunc(diffList)) + { + emit conversionAborted(); + return; + } + + QtConcurrent::run(this, &DbVersionConverter::fullConvertStep2); +} + +void DbVersionConverter::registerDbAfterSuccessfulConversion() +{ + DBLIST->addDb(fullConversionConfig->targetName, fullConversionConfig->targetFile); +} + +void DbVersionConverter::interrupt() +{ + setInterrupted(true); +} + +const QList >& DbVersionConverter::getDiffList() const +{ + return diffList; +} + +const QSet& DbVersionConverter::getErrors() const +{ + return errors; +} + +const QList&DbVersionConverter::getConverted() const +{ + return newQueries; +} + +QStringList DbVersionConverter::getConvertedSqls() const +{ + QStringList sqls; + for (SqliteQueryPtr query : newQueries) + sqls << query->detokenize(); + + return sqls; +} + +void DbVersionConverter::convertDb() +{ + resolver = new SchemaResolver(db); + resolver->setIgnoreSystemObjects(true); + StrHash parsedObjects = resolver->getAllParsedObjects(); + for (SqliteQueryPtr query : parsedObjects.values()) + { + switch (targetDialect) + { + case Dialect::Sqlite2: + convert3To2(query); + break; + case Dialect::Sqlite3: + convert2To3(query); + break; + } + } + sortConverted(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.h b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.h new file mode 100644 index 0000000..8343c2f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.h @@ -0,0 +1,133 @@ +#ifndef DBVERSIONCONVERTER_H +#define DBVERSIONCONVERTER_H + +#include "parser/ast/sqlitequery.h" +#include +#include +#include +#include + +class Db; +class SchemaResolver; +class SqliteCreateTable; +class SqliteCreateTrigger; +class SqliteCreateIndex; +class SqliteCreateView; +class SqliteCreateVirtualTable; +class SqliteIndexedColumn; +class SqliteSelect; +class SqliteDelete; +class SqliteUpdate; +class SqliteInsert; +class SqliteExpr; +class SqliteBeginTrans; + +class API_EXPORT DbVersionConverter : public QObject +{ + Q_OBJECT + + public: + typedef std::function>& diffs)> ConversionConfimFunction; + typedef std::function& errors)> ConversionErrorsConfimFunction; + + DbVersionConverter(); + virtual ~DbVersionConverter(); + + void convert(Dialect from, Dialect to, Db* srcDb, const QString& targetFile, const QString& targetName, ConversionConfimFunction confirmFunc, + ConversionErrorsConfimFunction errorsConfirmFunc); + void convert(Dialect from, Dialect to, Db* db); + void convert3To2(Db* db); + void convert2To3(Db* db); + QString convert(Dialect from, Dialect to, const QString& sql); + QString convert3To2(const QString& sql); + QString convert2To3(const QString& sql); + SqliteQueryPtr convert(Dialect from, Dialect to, SqliteQueryPtr query); + SqliteQueryPtr convert3To2(SqliteQueryPtr query); + SqliteQueryPtr convert2To3(SqliteQueryPtr query); + + const QList >& getDiffList() const; + const QSet& getErrors() const; + const QList& getConverted() const; + QStringList getConvertedSqls() const; + void reset(); + QList getSupportedVersions() const; + QStringList getSupportedVersionNames() const; + + private: + struct FullConversionConfig + { + Dialect from; + Dialect to; + Db* srcDb = nullptr; + QString targetFile; + QString targetName; + ConversionConfimFunction confirmFunc = nullptr; + ConversionErrorsConfimFunction errorsConfirmFunc = nullptr; + }; + + void fullConvertStep1(); + void fullConvertStep2(); + bool fullConvertCreateObjectsStep1(Db* db, QStringList& tables); + bool fullConvertCreateObjectsStep2(Db* db); + bool fullConvertCopyData(Db* db, const QStringList& tables); + bool checkForInterrupted(Db* db, bool rollback); + void convertDb(); + QList parse(const QString& sql, Dialect dialect); + bool modifySelectForVersion2(SqliteSelect* select); + bool modifyDeleteForVersion2(SqliteDelete* del); + bool modifyInsertForVersion2(SqliteInsert* insert); + bool modifyUpdateForVersion2(SqliteUpdate* update); + bool modifyCreateTableForVersion2(SqliteCreateTable* createTable); + bool modifyCreateTriggerForVersion2(SqliteCreateTrigger* createTrigger); + bool modifyCreateIndexForVersion2(SqliteCreateIndex* createIndex); + bool modifyCreateViewForVersion2(SqliteCreateView* createView); + bool modifyVirtualTableForVesion2(SqliteQueryPtr& query, SqliteCreateVirtualTable* createVirtualTable); + bool modifyAllExprsForVersion2(SqliteStatement* stmt); + bool modifySingleExprForVersion2(SqliteExpr* expr); + bool modifyAllIndexedColumnsForVersion2(SqliteStatement* stmt); + bool modifyAllIndexedColumnsForVersion2(const QList columns); + bool modifySingleIndexedColumnForVersion2(SqliteIndexedColumn* idxCol); + bool modifyBeginTransForVersion3(SqliteBeginTrans* begin); + bool modifyCreateTableForVersion3(SqliteCreateTable* createTable); + QString getSqlForDiff(SqliteStatement* stmt); + void storeDiff(const QString& sql1, SqliteStatement* stmt); + void storeErrorDiff(SqliteStatement* stmt); + QList getAllPossibleDbInstances() const; + QString generateQueryPlaceholders(int argCount); + void sortConverted(); + void setInterrupted(bool value); + bool isInterrupted(); + void conversionInterrupted(Db* db, bool rollback); + + template + QSharedPointer copyQuery(SqliteQueryPtr query) + { + return QSharedPointer::create(*(query.dynamicCast().data())); + } + + Db* db = nullptr; + Dialect targetDialect = Dialect::Sqlite3; + SchemaResolver* resolver = nullptr; + QList> diffList; + QSet errors; + QList newQueries; + FullConversionConfig* fullConversionConfig = nullptr; + bool interrupted = false; + QMutex interruptMutex; + + private slots: + void conversionError(Db* db, const QString& errMsg); + void confirmConversion(); + void registerDbAfterSuccessfulConversion(); + + public slots: + void interrupt(); + + signals: + void askUserForConfirmation(); + void conversionSuccessful(); + void conversionAborted(); + void conversionFailed(const QString& errorMsg); +}; + +#endif // DBVERSIONCONVERTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp b/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp new file mode 100644 index 0000000..9df3f81 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp @@ -0,0 +1,76 @@ +#include "ddlhistorymodel.h" +#include "querymodel.h" +#include +#include + +DdlHistoryModel::DdlHistoryModel(Db* db, QObject *parent) : + QSortFilterProxyModel(parent) +{ + static const QString query = + "SELECT dbname," + " file," + " date(timestamp, 'unixepoch') AS date," + " count(*)" + " FROM ddl_history" + " GROUP BY dbname, file, date" + " ORDER BY date DESC"; + + internalModel = new QueryModel(db, this); + setSourceModel(internalModel); + connect(internalModel, SIGNAL(refreshed()), this, SIGNAL(refreshed())); + + setFilterKeyColumn(0); + setDynamicSortFilter(true); + + internalModel->setQuery(query); +} + +QVariant DdlHistoryModel::data(const QModelIndex& index, int role) const +{ + if (role == Qt::TextAlignmentRole && (index.column() == 2 || index.column() == 3)) + return (int)(Qt::AlignRight|Qt::AlignVCenter); + + return QSortFilterProxyModel::data(index, role); +} + +void DdlHistoryModel::refresh() +{ + internalModel->refresh(); + +} + +void DdlHistoryModel::setDbNameForFilter(const QString& value) +{ + setFilterWildcard("*"+value+"*"); +} + +QStringList DdlHistoryModel::getDbNames() const +{ + QSet dbNames; + for (int row = 0; row < rowCount(); row++) + dbNames << data(index(row, 0)).toString(); + + QStringList nameList = dbNames.toList(); + qSort(nameList); + return nameList; +} + +QVariant DdlHistoryModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case 0: + return tr("Database name", "ddl history header"); + case 1: + return tr("Database file", "ddl history header"); + case 2: + return tr("Date of execution", "ddl history header"); + case 3: + return tr("Changes", "ddl history header"); + } + return QVariant(); + } + return QSortFilterProxyModel::headerData(section, orientation, role); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h b/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h new file mode 100644 index 0000000..8a82fb4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h @@ -0,0 +1,31 @@ +#ifndef DDLHISTORYMODEL_H +#define DDLHISTORYMODEL_H + +#include "coreSQLiteStudio_global.h" +#include + +class QueryModel; +class Db; + +class API_EXPORT DdlHistoryModel : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + DdlHistoryModel(Db* db, QObject *parent = nullptr); + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + void refresh(); + QString getDbNameForFilter() const; + void setDbNameForFilter(const QString& value); + QStringList getDbNames() const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + private: + QueryModel* internalModel = nullptr; + + signals: + void refreshed(); +}; + +#endif // DDLHISTORYMODEL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dialect.h b/SQLiteStudio3/coreSQLiteStudio/dialect.h new file mode 100644 index 0000000..f78b16a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dialect.h @@ -0,0 +1,10 @@ +#ifndef DIALECT_H +#define DIALECT_H + +enum class Dialect : int +{ + Sqlite3 = 0, + Sqlite2 = 1 +}; + +#endif // DIALECT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.cpp b/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.cpp new file mode 100644 index 0000000..4f0022e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.cpp @@ -0,0 +1,2112 @@ +/* + * Copyright 2008 Google Inc. All Rights Reserved. + * Author: fraser@google.com (Neil Fraser) + * Author: mikeslemmer@gmail.com (Mike Slemmer) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Diff Match and Patch + * http://code.google.com/p/google-diff-match-patch/ + */ + +/* + * Version slightly modified to compile with Qt 5.2 (2 minor fixes: toAscii() -> toLatin()). + */ + +#include +#include +// Code known to compile and run with Qt 4.3 through Qt 4.7. +#include +#include +#include "diff_match_patch.h" + + +////////////////////////// +// +// Diff Class +// +////////////////////////// + + +/** + * Constructor. Initializes the diff with the provided values. + * @param operation One of INSERT, DELETE or EQUAL + * @param text The text being applied + */ +Diff::Diff(Operation _operation, const QString &_text) : + operation(_operation), text(_text) { + // Construct a diff with the specified operation and text. +} + +Diff::Diff() { +} + + +QString Diff::strOperation(Operation op) { + switch (op) { + case INSERT: + return "INSERT"; + case DELETE: + return "DELETE"; + case EQUAL: + return "EQUAL"; + } + throw "Invalid operation."; +} + +/** + * Display a human-readable version of this Diff. + * @return text version + */ +QString Diff::toString() const { + QString prettyText = text; + // Replace linebreaks with Pilcrow signs. + prettyText.replace('\n', L'\u00b6'); + return QString("Diff(") + strOperation(operation) + QString(",\"") + + prettyText + QString("\")"); +} + +/** + * Is this Diff equivalent to another Diff? + * @param d Another Diff to compare against + * @return true or false + */ +bool Diff::operator==(const Diff &d) const { + return (d.operation == this->operation) && (d.text == this->text); +} + +bool Diff::operator!=(const Diff &d) const { + return !(operator == (d)); +} + + +///////////////////////////////////////////// +// +// Patch Class +// +///////////////////////////////////////////// + + +/** + * Constructor. Initializes with an empty list of diffs. + */ +Patch::Patch() : + start1(0), start2(0), + length1(0), length2(0) { +} + +bool Patch::isNull() const { + if (start1 == 0 && start2 == 0 && length1 == 0 && length2 == 0 + && diffs.size() == 0) { + return true; + } + return false; +} + + +/** + * Emmulate GNU diff's format. + * Header: @@ -382,8 +481,9 @@ + * Indicies are printed as 1-based, not 0-based. + * @return The GNU diff string + */ +QString Patch::toString() { + QString coords1, coords2; + if (length1 == 0) { + coords1 = QString::number(start1) + QString(",0"); + } else if (length1 == 1) { + coords1 = QString::number(start1 + 1); + } else { + coords1 = QString::number(start1 + 1) + QString(",") + + QString::number(length1); + } + if (length2 == 0) { + coords2 = QString::number(start2) + QString(",0"); + } else if (length2 == 1) { + coords2 = QString::number(start2 + 1); + } else { + coords2 = QString::number(start2 + 1) + QString(",") + + QString::number(length2); + } + QString text; + text = QString("@@ -") + coords1 + QString(" +") + coords2 + + QString(" @@\n"); + // Escape the body of the patch with %xx notation. + foreach (Diff aDiff, diffs) { + switch (aDiff.operation) { + case INSERT: + text += QString('+'); + break; + case DELETE: + text += QString('-'); + break; + case EQUAL: + text += QString(' '); + break; + } + text += QString(QUrl::toPercentEncoding(aDiff.text, " !~*'();/?:@&=+$,#")) + + QString("\n"); + } + + return text; +} + + +///////////////////////////////////////////// +// +// diff_match_patch Class +// +///////////////////////////////////////////// + +diff_match_patch::diff_match_patch() : + Diff_Timeout(1.0f), + Diff_EditCost(4), + Match_Threshold(0.5f), + Match_Distance(1000), + Patch_DeleteThreshold(0.5f), + Patch_Margin(4), + Match_MaxBits(32) { +} + + +QList diff_match_patch::diff_main(const QString &text1, + const QString &text2) { + return diff_main(text1, text2, true); +} + +QList diff_match_patch::diff_main(const QString &text1, + const QString &text2, bool checklines) { + // Set a deadline by which time the diff must be complete. + clock_t deadline; + if (Diff_Timeout <= 0) { + deadline = std::numeric_limits::max(); + } else { + deadline = clock() + (clock_t)(Diff_Timeout * CLOCKS_PER_SEC); + } + return diff_main(text1, text2, checklines, deadline); +} + +QList diff_match_patch::diff_main(const QString &text1, + const QString &text2, bool checklines, clock_t deadline) { + // Check for null inputs. + if (text1.isNull() || text2.isNull()) { + throw "Null inputs. (diff_main)"; + } + + // Check for equality (speedup). + QList diffs; + if (text1 == text2) { + if (!text1.isEmpty()) { + diffs.append(Diff(EQUAL, text1)); + } + return diffs; + } + + // Trim off common prefix (speedup). + int commonlength = diff_commonPrefix(text1, text2); + const QString &commonprefix = text1.left(commonlength); + QString textChopped1 = text1.mid(commonlength); + QString textChopped2 = text2.mid(commonlength); + + // Trim off common suffix (speedup). + commonlength = diff_commonSuffix(textChopped1, textChopped2); + const QString &commonsuffix = textChopped1.right(commonlength); + textChopped1 = textChopped1.left(textChopped1.length() - commonlength); + textChopped2 = textChopped2.left(textChopped2.length() - commonlength); + + // Compute the diff on the middle block. + diffs = diff_compute(textChopped1, textChopped2, checklines, deadline); + + // Restore the prefix and suffix. + if (!commonprefix.isEmpty()) { + diffs.prepend(Diff(EQUAL, commonprefix)); + } + if (!commonsuffix.isEmpty()) { + diffs.append(Diff(EQUAL, commonsuffix)); + } + + diff_cleanupMerge(diffs); + + return diffs; +} + + +QList diff_match_patch::diff_compute(QString text1, QString text2, + bool checklines, clock_t deadline) { + QList diffs; + + if (text1.isEmpty()) { + // Just add some text (speedup). + diffs.append(Diff(INSERT, text2)); + return diffs; + } + + if (text2.isEmpty()) { + // Just delete some text (speedup). + diffs.append(Diff(DELETE, text1)); + return diffs; + } + + { + const QString longtext = text1.length() > text2.length() ? text1 : text2; + const QString shorttext = text1.length() > text2.length() ? text2 : text1; + const int i = longtext.indexOf(shorttext); + if (i != -1) { + // Shorter text is inside the longer text (speedup). + const Operation op = (text1.length() > text2.length()) ? DELETE : INSERT; + diffs.append(Diff(op, longtext.left(i))); + diffs.append(Diff(EQUAL, shorttext)); + diffs.append(Diff(op, safeMid(longtext, i + shorttext.length()))); + return diffs; + } + + if (shorttext.length() == 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + diffs.append(Diff(DELETE, text1)); + diffs.append(Diff(INSERT, text2)); + return diffs; + } + // Garbage collect longtext and shorttext by scoping out. + } + + // Check to see if the problem can be split in two. + const QStringList hm = diff_halfMatch(text1, text2); + if (hm.count() > 0) { + // A half-match was found, sort out the return data. + const QString text1_a = hm[0]; + const QString text1_b = hm[1]; + const QString text2_a = hm[2]; + const QString text2_b = hm[3]; + const QString mid_common = hm[4]; + // Send both pairs off for separate processing. + const QList diffs_a = diff_main(text1_a, text2_a, + checklines, deadline); + const QList diffs_b = diff_main(text1_b, text2_b, + checklines, deadline); + // Merge the results. + diffs = diffs_a; + diffs.append(Diff(EQUAL, mid_common)); + diffs += diffs_b; + return diffs; + } + + // Perform a real diff. + if (checklines && text1.length() > 100 && text2.length() > 100) { + return diff_lineMode(text1, text2, deadline); + } + + return diff_bisect(text1, text2, deadline); +} + + +QList diff_match_patch::diff_lineMode(QString text1, QString text2, + clock_t deadline) { + // Scan the text on a line-by-line basis first. + const QList b = diff_linesToChars(text1, text2); + text1 = b[0].toString(); + text2 = b[1].toString(); + QStringList linearray = b[2].toStringList(); + + QList diffs = diff_main(text1, text2, false, deadline); + + // Convert the diff back to original text. + diff_charsToLines(diffs, linearray); + // Eliminate freak matches (e.g. blank lines) + diff_cleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.append(Diff(EQUAL, "")); + int count_delete = 0; + int count_insert = 0; + QString text_delete = ""; + QString text_insert = ""; + + QMutableListIterator pointer(diffs); + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + while (thisDiff != NULL) { + switch (thisDiff->operation) { + case INSERT: + count_insert++; + text_insert += thisDiff->text; + break; + case DELETE: + count_delete++; + text_delete += thisDiff->text; + break; + case EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete >= 1 && count_insert >= 1) { + // Delete the offending records and add the merged ones. + pointer.previous(); + for (int j = 0; j < count_delete + count_insert; j++) { + pointer.previous(); + pointer.remove(); + } + foreach(Diff newDiff, + diff_main(text_delete, text_insert, false, deadline)) { + pointer.insert(newDiff); + } + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + break; + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + diffs.removeLast(); // Remove the dummy entry at the end. + + return diffs; +} + + +QList diff_match_patch::diff_bisect(const QString &text1, + const QString &text2, clock_t deadline) { + // Cache the text lengths to prevent multiple calls. + const int text1_length = text1.length(); + const int text2_length = text2.length(); + const int max_d = (text1_length + text2_length + 1) / 2; + const int v_offset = max_d; + const int v_length = 2 * max_d; + int *v1 = new int[v_length]; + int *v2 = new int[v_length]; + for (int x = 0; x < v_length; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[v_offset + 1] = 0; + v2[v_offset + 1] = 0; + const int delta = text1_length - text2_length; + // If the total number of characters is odd, then the front path will + // collide with the reverse path. + const bool front = (delta % 2 != 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + int k1start = 0; + int k1end = 0; + int k2start = 0; + int k2end = 0; + for (int d = 0; d < max_d; d++) { + // Bail out if deadline is reached. + if (clock() > deadline) { + break; + } + + // Walk the front path one step. + for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + const int k1_offset = v_offset + k1; + int x1; + if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { + x1 = v1[k1_offset + 1]; + } else { + x1 = v1[k1_offset - 1] + 1; + } + int y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length + && text1[x1] == text2[y1]) { + x1++; + y1++; + } + v1[k1_offset] = x1; + if (x1 > text1_length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2_length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + int k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { + // Mirror x2 onto top-left coordinate system. + int x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) { + // Overlap detected. + delete [] v1; + delete [] v2; + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + + // Walk the reverse path one step. + for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + const int k2_offset = v_offset + k2; + int x2; + if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { + x2 = v2[k2_offset + 1]; + } else { + x2 = v2[k2_offset - 1] + 1; + } + int y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length + && text1[text1_length - x2 - 1] == text2[text2_length - y2 - 1]) { + x2++; + y2++; + } + v2[k2_offset] = x2; + if (x2 > text1_length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2_length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + int k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { + int x1 = v1[k1_offset]; + int y1 = v_offset + x1 - k1_offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1_length - x2; + if (x1 >= x2) { + // Overlap detected. + delete [] v1; + delete [] v2; + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + } + delete [] v1; + delete [] v2; + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + QList diffs; + diffs.append(Diff(DELETE, text1)); + diffs.append(Diff(INSERT, text2)); + return diffs; +} + +QList diff_match_patch::diff_bisectSplit(const QString &text1, + const QString &text2, int x, int y, clock_t deadline) { + QString text1a = text1.left(x); + QString text2a = text2.left(y); + QString text1b = safeMid(text1, x); + QString text2b = safeMid(text2, y); + + // Compute both diffs serially. + QList diffs = diff_main(text1a, text2a, false, deadline); + QList diffsb = diff_main(text1b, text2b, false, deadline); + + return diffs + diffsb; +} + +QList diff_match_patch::diff_linesToChars(const QString &text1, + const QString &text2) { + QStringList lineArray; + QMap lineHash; + // e.g. linearray[4] == "Hello\n" + // e.g. linehash.get("Hello\n") == 4 + + // "\x00" is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray.append(""); + + const QString chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash); + const QString chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash); + + QList listRet; + listRet.append(QVariant::fromValue(chars1)); + listRet.append(QVariant::fromValue(chars2)); + listRet.append(QVariant::fromValue(lineArray)); + return listRet; +} + + +QString diff_match_patch::diff_linesToCharsMunge(const QString &text, + QStringList &lineArray, + QMap &lineHash) { + int lineStart = 0; + int lineEnd = -1; + QString line; + QString chars; + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + while (lineEnd < text.length() - 1) { + lineEnd = text.indexOf('\n', lineStart); + if (lineEnd == -1) { + lineEnd = text.length() - 1; + } + line = safeMid(text, lineStart, lineEnd + 1 - lineStart); + lineStart = lineEnd + 1; + + if (lineHash.contains(line)) { + chars += QChar(static_cast(lineHash.value(line))); + } else { + lineArray.append(line); + lineHash.insert(line, lineArray.size() - 1); + chars += QChar(static_cast(lineArray.size() - 1)); + } + } + return chars; +} + + + +void diff_match_patch::diff_charsToLines(QList &diffs, + const QStringList &lineArray) { + // Qt has no mutable foreach construct. + QMutableListIterator i(diffs); + while (i.hasNext()) { + Diff &diff = i.next(); + QString text; + for (int y = 0; y < diff.text.length(); y++) { + text += lineArray.value(static_cast(diff.text[y].unicode())); + } + diff.text = text; + } +} + + +int diff_match_patch::diff_commonPrefix(const QString &text1, + const QString &text2) { + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + const int n = std::min(text1.length(), text2.length()); + for (int i = 0; i < n; i++) { + if (text1[i] != text2[i]) { + return i; + } + } + return n; +} + + +int diff_match_patch::diff_commonSuffix(const QString &text1, + const QString &text2) { + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + const int text1_length = text1.length(); + const int text2_length = text2.length(); + const int n = std::min(text1_length, text2_length); + for (int i = 1; i <= n; i++) { + if (text1[text1_length - i] != text2[text2_length - i]) { + return i - 1; + } + } + return n; +} + +int diff_match_patch::diff_commonOverlap(const QString &text1, + const QString &text2) { + // Cache the text lengths to prevent multiple calls. + const int text1_length = text1.length(); + const int text2_length = text2.length(); + // Eliminate the null case. + if (text1_length == 0 || text2_length == 0) { + return 0; + } + // Truncate the longer string. + QString text1_trunc = text1; + QString text2_trunc = text2; + if (text1_length > text2_length) { + text1_trunc = text1.right(text2_length); + } else if (text1_length < text2_length) { + text2_trunc = text2.left(text1_length); + } + const int text_length = std::min(text1_length, text2_length); + // Quick check for the worst case. + if (text1_trunc == text2_trunc) { + return text_length; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: http://neil.fraser.name/news/2010/11/04/ + int best = 0; + int length = 1; + while (true) { + QString pattern = text1_trunc.right(length); + int found = text2_trunc.indexOf(pattern); + if (found == -1) { + return best; + } + length += found; + if (found == 0 || text1_trunc.right(length) == text2_trunc.left(length)) { + best = length; + length++; + } + } +} + +QStringList diff_match_patch::diff_halfMatch(const QString &text1, + const QString &text2) { + if (Diff_Timeout <= 0) { + // Don't risk returning a non-optimal diff if we have unlimited time. + return QStringList(); + } + const QString longtext = text1.length() > text2.length() ? text1 : text2; + const QString shorttext = text1.length() > text2.length() ? text2 : text1; + if (longtext.length() < 4 || shorttext.length() * 2 < longtext.length()) { + return QStringList(); // Pointless. + } + + // First check if the second quarter is the seed for a half-match. + const QStringList hm1 = diff_halfMatchI(longtext, shorttext, + (longtext.length() + 3) / 4); + // Check again based on the third quarter. + const QStringList hm2 = diff_halfMatchI(longtext, shorttext, + (longtext.length() + 1) / 2); + QStringList hm; + if (hm1.isEmpty() && hm2.isEmpty()) { + return QStringList(); + } else if (hm2.isEmpty()) { + hm = hm1; + } else if (hm1.isEmpty()) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + if (text1.length() > text2.length()) { + return hm; + } else { + QStringList listRet; + listRet << hm[2] << hm[3] << hm[0] << hm[1] << hm[4]; + return listRet; + } +} + + +QStringList diff_match_patch::diff_halfMatchI(const QString &longtext, + const QString &shorttext, + int i) { + // Start with a 1/4 length substring at position i as a seed. + const QString seed = safeMid(longtext, i, longtext.length() / 4); + int j = -1; + QString best_common; + QString best_longtext_a, best_longtext_b; + QString best_shorttext_a, best_shorttext_b; + while ((j = shorttext.indexOf(seed, j + 1)) != -1) { + const int prefixLength = diff_commonPrefix(safeMid(longtext, i), + safeMid(shorttext, j)); + const int suffixLength = diff_commonSuffix(longtext.left(i), + shorttext.left(j)); + if (best_common.length() < suffixLength + prefixLength) { + best_common = safeMid(shorttext, j - suffixLength, suffixLength) + + safeMid(shorttext, j, prefixLength); + best_longtext_a = longtext.left(i - suffixLength); + best_longtext_b = safeMid(longtext, i + prefixLength); + best_shorttext_a = shorttext.left(j - suffixLength); + best_shorttext_b = safeMid(shorttext, j + prefixLength); + } + } + if (best_common.length() * 2 >= longtext.length()) { + QStringList listRet; + listRet << best_longtext_a << best_longtext_b << best_shorttext_a + << best_shorttext_b << best_common; + return listRet; + } else { + return QStringList(); + } +} + + +void diff_match_patch::diff_cleanupSemantic(QList &diffs) { + if (diffs.isEmpty()) { + return; + } + bool changes = false; + QStack equalities; // Stack of equalities. + QString lastequality; // Always equal to equalities.lastElement().text + QMutableListIterator pointer(diffs); + // Number of characters that changed prior to the equality. + int length_insertions1 = 0; + int length_deletions1 = 0; + // Number of characters that changed after the equality. + int length_insertions2 = 0; + int length_deletions2 = 0; + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + while (thisDiff != NULL) { + if (thisDiff->operation == EQUAL) { + // Equality found. + equalities.push(*thisDiff); + length_insertions1 = length_insertions2; + length_deletions1 = length_deletions2; + length_insertions2 = 0; + length_deletions2 = 0; + lastequality = thisDiff->text; + } else { + // An insertion or deletion. + if (thisDiff->operation == INSERT) { + length_insertions2 += thisDiff->text.length(); + } else { + length_deletions2 += thisDiff->text.length(); + } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (!lastequality.isNull() + && (lastequality.length() + <= std::max(length_insertions1, length_deletions1)) + && (lastequality.length() + <= std::max(length_insertions2, length_deletions2))) { + // printf("Splitting: '%s'\n", qPrintable(lastequality)); + // Walk back to offending equality. + while (*thisDiff != equalities.top()) { + thisDiff = &pointer.previous(); + } + pointer.next(); + + // Replace equality with a delete. + pointer.setValue(Diff(DELETE, lastequality)); + // Insert a corresponding an insert. + pointer.insert(Diff(INSERT, lastequality)); + + equalities.pop(); // Throw away the equality we just deleted. + if (!equalities.isEmpty()) { + // Throw away the previous equality (it needs to be reevaluated). + equalities.pop(); + } + if (equalities.isEmpty()) { + // There are no previous equalities, walk back to the start. + while (pointer.hasPrevious()) { + pointer.previous(); + } + } else { + // There is a safe equality we can fall back to. + thisDiff = &equalities.top(); + while (*thisDiff != pointer.previous()) { + // Intentionally empty loop. + } + } + + length_insertions1 = 0; // Reset the counters. + length_deletions1 = 0; + length_insertions2 = 0; + length_deletions2 = 0; + lastequality = QString(); + changes = true; + } + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + + // Normalize the diff. + if (changes) { + diff_cleanupMerge(diffs); + } + diff_cleanupSemanticLossless(diffs); + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer.toFront(); + Diff *prevDiff = NULL; + thisDiff = NULL; + if (pointer.hasNext()) { + prevDiff = &pointer.next(); + if (pointer.hasNext()) { + thisDiff = &pointer.next(); + } + } + while (thisDiff != NULL) { + if (prevDiff->operation == DELETE && + thisDiff->operation == INSERT) { + QString deletion = prevDiff->text; + QString insertion = thisDiff->text; + int overlap_length1 = diff_commonOverlap(deletion, insertion); + int overlap_length2 = diff_commonOverlap(insertion, deletion); + if (overlap_length1 >= overlap_length2) { + if (overlap_length1 >= deletion.length() / 2.0 || + overlap_length1 >= insertion.length() / 2.0) { + // Overlap found. Insert an equality and trim the surrounding edits. + pointer.previous(); + pointer.insert(Diff(EQUAL, insertion.left(overlap_length1))); + prevDiff->text = + deletion.left(deletion.length() - overlap_length1); + thisDiff->text = safeMid(insertion, overlap_length1); + // pointer.insert inserts the element before the cursor, so there is + // no need to step past the new element. + } + } else { + if (overlap_length2 >= deletion.length() / 2.0 || + overlap_length2 >= insertion.length() / 2.0) { + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + pointer.previous(); + pointer.insert(Diff(EQUAL, deletion.left(overlap_length2))); + prevDiff->operation = INSERT; + prevDiff->text = + insertion.left(insertion.length() - overlap_length2); + thisDiff->operation = DELETE; + thisDiff->text = safeMid(deletion, overlap_length2); + // pointer.insert inserts the element before the cursor, so there is + // no need to step past the new element. + } + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + prevDiff = thisDiff; + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } +} + + +void diff_match_patch::diff_cleanupSemanticLossless(QList &diffs) { + QString equality1, edit, equality2; + QString commonString; + int commonOffset; + int score, bestScore; + QString bestEquality1, bestEdit, bestEquality2; + // Create a new iterator at the start. + QMutableListIterator pointer(diffs); + Diff *prevDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + + // Intentionally ignore the first and last element (don't need checking). + while (nextDiff != NULL) { + if (prevDiff->operation == EQUAL && + nextDiff->operation == EQUAL) { + // This is a single edit surrounded by equalities. + equality1 = prevDiff->text; + edit = thisDiff->text; + equality2 = nextDiff->text; + + // First, shift the edit as far left as possible. + commonOffset = diff_commonSuffix(equality1, edit); + if (commonOffset != 0) { + commonString = safeMid(edit, edit.length() - commonOffset); + equality1 = equality1.left(equality1.length() - commonOffset); + edit = commonString + edit.left(edit.length() - commonOffset); + equality2 = commonString + equality2; + } + + // Second, step character by character right, looking for the best fit. + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + bestScore = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + while (!edit.isEmpty() && !equality2.isEmpty() + && edit[0] == equality2[0]) { + equality1 += edit[0]; + edit = safeMid(edit, 1) + equality2[0]; + equality2 = safeMid(equality2, 1); + score = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + // The >= encourages trailing rather than leading whitespace on edits. + if (score >= bestScore) { + bestScore = score; + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + } + } + + if (prevDiff->text != bestEquality1) { + // We have an improvement, save it back to the diff. + if (!bestEquality1.isEmpty()) { + prevDiff->text = bestEquality1; + } else { + pointer.previous(); // Walk past nextDiff. + pointer.previous(); // Walk past thisDiff. + pointer.previous(); // Walk past prevDiff. + pointer.remove(); // Delete prevDiff. + pointer.next(); // Walk past thisDiff. + pointer.next(); // Walk past nextDiff. + } + thisDiff->text = bestEdit; + if (!bestEquality2.isEmpty()) { + nextDiff->text = bestEquality2; + } else { + pointer.remove(); // Delete nextDiff. + nextDiff = thisDiff; + thisDiff = prevDiff; + } + } + } + prevDiff = thisDiff; + thisDiff = nextDiff; + nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + } +} + + +int diff_match_patch::diff_cleanupSemanticScore(const QString &one, + const QString &two) { + if (one.isEmpty() || two.isEmpty()) { + // Edges are the best. + return 6; + } + + // Each port of this function behaves slightly differently due to + // subtle differences in each language's definition of things like + // 'whitespace'. Since this function's purpose is largely cosmetic, + // the choice has been made to use each language's native features + // rather than force total conformity. + QChar char1 = one[one.length() - 1]; + QChar char2 = two[0]; + bool nonAlphaNumeric1 = !char1.isLetterOrNumber(); + bool nonAlphaNumeric2 = !char2.isLetterOrNumber(); + bool whitespace1 = nonAlphaNumeric1 && char1.isSpace(); + bool whitespace2 = nonAlphaNumeric2 && char2.isSpace(); + bool lineBreak1 = whitespace1 && char1.category() == QChar::Other_Control; + bool lineBreak2 = whitespace2 && char2.category() == QChar::Other_Control; + bool blankLine1 = lineBreak1 && BLANKLINEEND.indexIn(one) != -1; + bool blankLine2 = lineBreak2 && BLANKLINESTART.indexIn(two) != -1; + + if (blankLine1 || blankLine2) { + // Five points for blank lines. + return 5; + } else if (lineBreak1 || lineBreak2) { + // Four points for line breaks. + return 4; + } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { + // Three points for end of sentences. + return 3; + } else if (whitespace1 || whitespace2) { + // Two points for whitespace. + return 2; + } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { + // One point for non-alphanumeric. + return 1; + } + return 0; +} + + +// Define some regex patterns for matching boundaries. +QRegExp diff_match_patch::BLANKLINEEND = QRegExp("\\n\\r?\\n$"); +QRegExp diff_match_patch::BLANKLINESTART = QRegExp("^\\r?\\n\\r?\\n"); + + +void diff_match_patch::diff_cleanupEfficiency(QList &diffs) { + if (diffs.isEmpty()) { + return; + } + bool changes = false; + QStack equalities; // Stack of equalities. + QString lastequality; // Always equal to equalities.lastElement().text + QMutableListIterator pointer(diffs); + // Is there an insertion operation before the last equality. + bool pre_ins = false; + // Is there a deletion operation before the last equality. + bool pre_del = false; + // Is there an insertion operation after the last equality. + bool post_ins = false; + // Is there a deletion operation after the last equality. + bool post_del = false; + + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *safeDiff = thisDiff; + + while (thisDiff != NULL) { + if (thisDiff->operation == EQUAL) { + // Equality found. + if (thisDiff->text.length() < Diff_EditCost && (post_ins || post_del)) { + // Candidate found. + equalities.push(*thisDiff); + pre_ins = post_ins; + pre_del = post_del; + lastequality = thisDiff->text; + } else { + // Not a candidate, and can never become one. + equalities.clear(); + lastequality = QString(); + safeDiff = thisDiff; + } + post_ins = post_del = false; + } else { + // An insertion or deletion. + if (thisDiff->operation == DELETE) { + post_del = true; + } else { + post_ins = true; + } + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if (!lastequality.isNull() + && ((pre_ins && pre_del && post_ins && post_del) + || ((lastequality.length() < Diff_EditCost / 2) + && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) + + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) { + // printf("Splitting: '%s'\n", qPrintable(lastequality)); + // Walk back to offending equality. + while (*thisDiff != equalities.top()) { + thisDiff = &pointer.previous(); + } + pointer.next(); + + // Replace equality with a delete. + pointer.setValue(Diff(DELETE, lastequality)); + // Insert a corresponding an insert. + pointer.insert(Diff(INSERT, lastequality)); + thisDiff = &pointer.previous(); + pointer.next(); + + equalities.pop(); // Throw away the equality we just deleted. + lastequality = QString(); + if (pre_ins && pre_del) { + // No changes made which could affect previous entry, keep going. + post_ins = post_del = true; + equalities.clear(); + safeDiff = thisDiff; + } else { + if (!equalities.isEmpty()) { + // Throw away the previous equality (it needs to be reevaluated). + equalities.pop(); + } + if (equalities.isEmpty()) { + // There are no previous questionable equalities, + // walk back to the last known safe diff. + thisDiff = safeDiff; + } else { + // There is an equality we can fall back to. + thisDiff = &equalities.top(); + } + while (*thisDiff != pointer.previous()) { + // Intentionally empty loop. + } + post_ins = post_del = false; + } + + changes = true; + } + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + + if (changes) { + diff_cleanupMerge(diffs); + } +} + + +void diff_match_patch::diff_cleanupMerge(QList &diffs) { + diffs.append(Diff(EQUAL, "")); // Add a dummy entry at the end. + QMutableListIterator pointer(diffs); + int count_delete = 0; + int count_insert = 0; + QString text_delete = ""; + QString text_insert = ""; + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *prevEqual = NULL; + int commonlength; + while (thisDiff != NULL) { + switch (thisDiff->operation) { + case INSERT: + count_insert++; + text_insert += thisDiff->text; + prevEqual = NULL; + break; + case DELETE: + count_delete++; + text_delete += thisDiff->text; + prevEqual = NULL; + break; + case EQUAL: + if (count_delete + count_insert > 1) { + bool both_types = count_delete != 0 && count_insert != 0; + // Delete the offending records. + pointer.previous(); // Reverse direction. + while (count_delete-- > 0) { + pointer.previous(); + pointer.remove(); + } + while (count_insert-- > 0) { + pointer.previous(); + pointer.remove(); + } + if (both_types) { + // Factor out any common prefixies. + commonlength = diff_commonPrefix(text_insert, text_delete); + if (commonlength != 0) { + if (pointer.hasPrevious()) { + thisDiff = &pointer.previous(); + if (thisDiff->operation != EQUAL) { + throw "Previous diff should have been an equality."; + } + thisDiff->text += text_insert.left(commonlength); + pointer.next(); + } else { + pointer.insert(Diff(EQUAL, text_insert.left(commonlength))); + } + text_insert = safeMid(text_insert, commonlength); + text_delete = safeMid(text_delete, commonlength); + } + // Factor out any common suffixies. + commonlength = diff_commonSuffix(text_insert, text_delete); + if (commonlength != 0) { + thisDiff = &pointer.next(); + thisDiff->text = safeMid(text_insert, text_insert.length() + - commonlength) + thisDiff->text; + text_insert = text_insert.left(text_insert.length() + - commonlength); + text_delete = text_delete.left(text_delete.length() + - commonlength); + pointer.previous(); + } + } + // Insert the merged records. + if (!text_delete.isEmpty()) { + pointer.insert(Diff(DELETE, text_delete)); + } + if (!text_insert.isEmpty()) { + pointer.insert(Diff(INSERT, text_insert)); + } + // Step forward to the equality. + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + + } else if (prevEqual != NULL) { + // Merge this equality with the previous one. + prevEqual->text += thisDiff->text; + pointer.remove(); + thisDiff = &pointer.previous(); + pointer.next(); // Forward direction + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + prevEqual = thisDiff; + break; + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + if (diffs.back().text.isEmpty()) { + diffs.removeLast(); // Remove the dummy entry at the end. + } + + /* + * Second pass: look for single edits surrounded on both sides by equalities + * which can be shifted sideways to eliminate an equality. + * e.g: ABAC -> ABAC + */ + bool changes = false; + // Create a new iterator at the start. + // (As opposed to walking the current one back.) + pointer.toFront(); + Diff *prevDiff = pointer.hasNext() ? &pointer.next() : NULL; + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + + // Intentionally ignore the first and last element (don't need checking). + while (nextDiff != NULL) { + if (prevDiff->operation == EQUAL && + nextDiff->operation == EQUAL) { + // This is a single edit surrounded by equalities. + if (thisDiff->text.endsWith(prevDiff->text)) { + // Shift the edit over the previous equality. + thisDiff->text = prevDiff->text + + thisDiff->text.left(thisDiff->text.length() + - prevDiff->text.length()); + nextDiff->text = prevDiff->text + nextDiff->text; + pointer.previous(); // Walk past nextDiff. + pointer.previous(); // Walk past thisDiff. + pointer.previous(); // Walk past prevDiff. + pointer.remove(); // Delete prevDiff. + pointer.next(); // Walk past thisDiff. + thisDiff = &pointer.next(); // Walk past nextDiff. + nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + changes = true; + } else if (thisDiff->text.startsWith(nextDiff->text)) { + // Shift the edit over the next equality. + prevDiff->text += nextDiff->text; + thisDiff->text = safeMid(thisDiff->text, nextDiff->text.length()) + + nextDiff->text; + pointer.remove(); // Delete nextDiff. + nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + changes = true; + } + } + prevDiff = thisDiff; + thisDiff = nextDiff; + nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + diff_cleanupMerge(diffs); + } +} + + +int diff_match_patch::diff_xIndex(const QList &diffs, int loc) { + int chars1 = 0; + int chars2 = 0; + int last_chars1 = 0; + int last_chars2 = 0; + Diff lastDiff; + foreach(Diff aDiff, diffs) { + if (aDiff.operation != INSERT) { + // Equality or deletion. + chars1 += aDiff.text.length(); + } + if (aDiff.operation != DELETE) { + // Equality or insertion. + chars2 += aDiff.text.length(); + } + if (chars1 > loc) { + // Overshot the location. + lastDiff = aDiff; + break; + } + last_chars1 = chars1; + last_chars2 = chars2; + } + if (lastDiff.operation == DELETE) { + // The location was deleted. + return last_chars2; + } + // Add the remaining character length. + return last_chars2 + (loc - last_chars1); +} + + +QString diff_match_patch::diff_prettyHtml(const QList &diffs) { + QString html; + QString text; + foreach(Diff aDiff, diffs) { + text = aDiff.text; + text.replace("&", "&").replace("<", "<") + .replace(">", ">").replace("\n", "¶
"); + switch (aDiff.operation) { + case INSERT: + html += QString("") + text + + QString(""); + break; + case DELETE: + html += QString("") + text + + QString(""); + break; + case EQUAL: + html += QString("") + text + QString(""); + break; + } + } + return html; +} + + +QString diff_match_patch::diff_text1(const QList &diffs) { + QString text; + foreach(Diff aDiff, diffs) { + if (aDiff.operation != INSERT) { + text += aDiff.text; + } + } + return text; +} + + +QString diff_match_patch::diff_text2(const QList &diffs) { + QString text; + foreach(Diff aDiff, diffs) { + if (aDiff.operation != DELETE) { + text += aDiff.text; + } + } + return text; +} + + +int diff_match_patch::diff_levenshtein(const QList &diffs) { + int levenshtein = 0; + int insertions = 0; + int deletions = 0; + foreach(Diff aDiff, diffs) { + switch (aDiff.operation) { + case INSERT: + insertions += aDiff.text.length(); + break; + case DELETE: + deletions += aDiff.text.length(); + break; + case EQUAL: + // A deletion and an insertion is one substitution. + levenshtein += std::max(insertions, deletions); + insertions = 0; + deletions = 0; + break; + } + } + levenshtein += std::max(insertions, deletions); + return levenshtein; +} + + +QString diff_match_patch::diff_toDelta(const QList &diffs) { + QString text; + foreach(Diff aDiff, diffs) { + switch (aDiff.operation) { + case INSERT: { + QString encoded = QString(QUrl::toPercentEncoding(aDiff.text, + " !~*'();/?:@&=+$,#")); + text += QString("+") + encoded + QString("\t"); + break; + } + case DELETE: + text += QString("-") + QString::number(aDiff.text.length()) + + QString("\t"); + break; + case EQUAL: + text += QString("=") + QString::number(aDiff.text.length()) + + QString("\t"); + break; + } + } + if (!text.isEmpty()) { + // Strip off trailing tab character. + text = text.left(text.length() - 1); + } + return text; +} + + +QList diff_match_patch::diff_fromDelta(const QString &text1, + const QString &delta) { + QList diffs; + int pointer = 0; // Cursor in text1 + QStringList tokens = delta.split("\t"); + foreach(QString token, tokens) { + if (token.isEmpty()) { + // Blank tokens are ok (from a trailing \t). + continue; + } + // Each token begins with a one character parameter which specifies the + // operation of this token (delete, insert, equality). + QString param = safeMid(token, 1); + switch (token[0].toLatin1()) { + case '+': + param = QUrl::fromPercentEncoding(qPrintable(param)); + diffs.append(Diff(INSERT, param)); + break; + case '-': + // Fall through. + case '=': { + int n; + n = param.toInt(); + if (n < 0) { + throw QString("Negative number in diff_fromDelta: %1").arg(param); + } + QString text; + text = safeMid(text1, pointer, n); + pointer += n; + if (token[0] == QChar('=')) { + diffs.append(Diff(EQUAL, text)); + } else { + diffs.append(Diff(DELETE, text)); + } + break; + } + default: + throw QString("Invalid diff operation in diff_fromDelta: %1") + .arg(token[0]); + } + } + if (pointer != text1.length()) { + throw QString("Delta length (%1) smaller than source text length (%2)") + .arg(pointer).arg(text1.length()); + } + return diffs; +} + + + // MATCH FUNCTIONS + + +int diff_match_patch::match_main(const QString &text, const QString &pattern, + int loc) { + // Check for null inputs. + if (text.isNull() || pattern.isNull()) { + throw "Null inputs. (match_main)"; + } + + loc = std::max(0, std::min(loc, text.length())); + if (text == pattern) { + // Shortcut (potentially not guaranteed by the algorithm) + return 0; + } else if (text.isEmpty()) { + // Nothing to match. + return -1; + } else if (loc + pattern.length() <= text.length() + && safeMid(text, loc, pattern.length()) == pattern) { + // Perfect match at the perfect spot! (Includes case of null pattern) + return loc; + } else { + // Do a fuzzy compare. + return match_bitap(text, pattern, loc); + } +} + + +int diff_match_patch::match_bitap(const QString &text, const QString &pattern, + int loc) { + if (!(Match_MaxBits == 0 || pattern.length() <= Match_MaxBits)) { + throw "Pattern too long for this application."; + } + + // Initialise the alphabet. + QMap s = match_alphabet(pattern); + + // Highest score beyond which we give up. + double score_threshold = Match_Threshold; + // Is there a nearby exact match? (speedup) + int best_loc = text.indexOf(pattern, loc); + if (best_loc != -1) { + score_threshold = std::min(match_bitapScore(0, best_loc, loc, pattern), + score_threshold); + // What about in the other direction? (speedup) + best_loc = text.lastIndexOf(pattern, loc + pattern.length()); + if (best_loc != -1) { + score_threshold = std::min(match_bitapScore(0, best_loc, loc, pattern), + score_threshold); + } + } + + // Initialise the bit arrays. + int matchmask = 1 << (pattern.length() - 1); + best_loc = -1; + + int bin_min, bin_mid; + int bin_max = pattern.length() + text.length(); + int *rd; + int *last_rd = NULL; + for (int d = 0; d < pattern.length(); d++) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from 'loc' we can stray at + // this error level. + bin_min = 0; + bin_mid = bin_max; + while (bin_min < bin_mid) { + if (match_bitapScore(d, loc + bin_mid, loc, pattern) + <= score_threshold) { + bin_min = bin_mid; + } else { + bin_max = bin_mid; + } + bin_mid = (bin_max - bin_min) / 2 + bin_min; + } + // Use the result from this iteration as the maximum for the next. + bin_max = bin_mid; + int start = std::max(1, loc - bin_mid + 1); + int finish = std::min(loc + bin_mid, text.length()) + pattern.length(); + + rd = new int[finish + 2]; + rd[finish + 1] = (1 << d) - 1; + for (int j = finish; j >= start; j--) { + int charMatch; + if (text.length() <= j - 1) { + // Out of range. + charMatch = 0; + } else { + charMatch = s.value(text[j - 1], 0); + } + if (d == 0) { + // First pass: exact match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; + } else { + // Subsequent passes: fuzzy match. + rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) + | last_rd[j + 1]; + } + if ((rd[j] & matchmask) != 0) { + double score = match_bitapScore(d, j - 1, loc, pattern); + // This match will almost certainly be better than any existing + // match. But check anyway. + if (score <= score_threshold) { + // Told you so. + score_threshold = score; + best_loc = j - 1; + if (best_loc > loc) { + // When passing loc, don't exceed our current distance from loc. + start = std::max(1, 2 * loc - best_loc); + } else { + // Already passed loc, downhill from here on in. + break; + } + } + } + } + if (match_bitapScore(d + 1, loc, loc, pattern) > score_threshold) { + // No hope for a (better) match at greater error levels. + break; + } + delete [] last_rd; + last_rd = rd; + } + delete [] last_rd; + delete [] rd; + return best_loc; +} + + +double diff_match_patch::match_bitapScore(int e, int x, int loc, + const QString &pattern) { + const float accuracy = static_cast (e) / pattern.length(); + const int proximity = qAbs(loc - x); + if (Match_Distance == 0) { + // Dodge divide by zero error. + return proximity == 0 ? accuracy : 1.0; + } + return accuracy + (proximity / static_cast (Match_Distance)); +} + + +QMap diff_match_patch::match_alphabet(const QString &pattern) { + QMap s; + int i; + for (i = 0; i < pattern.length(); i++) { + QChar c = pattern[i]; + s.insert(c, 0); + } + for (i = 0; i < pattern.length(); i++) { + QChar c = pattern[i]; + s.insert(c, s.value(c) | (1 << (pattern.length() - i - 1))); + } + return s; +} + + +// PATCH FUNCTIONS + + +void diff_match_patch::patch_addContext(Patch &patch, const QString &text) { + if (text.isEmpty()) { + return; + } + QString pattern = safeMid(text, patch.start2, patch.length1); + int padding = 0; + + // Look for the first and last matches of pattern in text. If two different + // matches are found, increase the pattern length. + while (text.indexOf(pattern) != text.lastIndexOf(pattern) + && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) { + padding += Patch_Margin; + pattern = safeMid(text, std::max(0, patch.start2 - padding), + std::min(text.length(), patch.start2 + patch.length1 + padding) + - std::max(0, patch.start2 - padding)); + } + // Add one chunk for good luck. + padding += Patch_Margin; + + // Add the prefix. + QString prefix = safeMid(text, std::max(0, patch.start2 - padding), + patch.start2 - std::max(0, patch.start2 - padding)); + if (!prefix.isEmpty()) { + patch.diffs.prepend(Diff(EQUAL, prefix)); + } + // Add the suffix. + QString suffix = safeMid(text, patch.start2 + patch.length1, + std::min(text.length(), patch.start2 + patch.length1 + padding) + - (patch.start2 + patch.length1)); + if (!suffix.isEmpty()) { + patch.diffs.append(Diff(EQUAL, suffix)); + } + + // Roll back the start points. + patch.start1 -= prefix.length(); + patch.start2 -= prefix.length(); + // Extend the lengths. + patch.length1 += prefix.length() + suffix.length(); + patch.length2 += prefix.length() + suffix.length(); +} + + +QList diff_match_patch::patch_make(const QString &text1, + const QString &text2) { + // Check for null inputs. + if (text1.isNull() || text2.isNull()) { + throw "Null inputs. (patch_make)"; + } + + // No diffs provided, compute our own. + QList diffs = diff_main(text1, text2, true); + if (diffs.size() > 2) { + diff_cleanupSemantic(diffs); + diff_cleanupEfficiency(diffs); + } + + return patch_make(text1, diffs); +} + + +QList diff_match_patch::patch_make(const QList &diffs) { + // No origin string provided, compute our own. + const QString text1 = diff_text1(diffs); + return patch_make(text1, diffs); +} + + +QList diff_match_patch::patch_make(const QString &text1, + const QString &text2, + const QList &diffs) { + // text2 is entirely unused. + return patch_make(text1, diffs); + + Q_UNUSED(text2) +} + + +QList diff_match_patch::patch_make(const QString &text1, + const QList &diffs) { + // Check for null inputs. + if (text1.isNull()) { + throw "Null inputs. (patch_make)"; + } + + QList patches; + if (diffs.isEmpty()) { + return patches; // Get rid of the null case. + } + Patch patch; + int char_count1 = 0; // Number of characters into the text1 string. + int char_count2 = 0; // Number of characters into the text2 string. + // Start with text1 (prepatch_text) and apply the diffs until we arrive at + // text2 (postpatch_text). We recreate the patches one by one to determine + // context info. + QString prepatch_text = text1; + QString postpatch_text = text1; + foreach(Diff aDiff, diffs) { + if (patch.diffs.isEmpty() && aDiff.operation != EQUAL) { + // A new patch starts here. + patch.start1 = char_count1; + patch.start2 = char_count2; + } + + switch (aDiff.operation) { + case INSERT: + patch.diffs.append(aDiff); + patch.length2 += aDiff.text.length(); + postpatch_text = postpatch_text.left(char_count2) + + aDiff.text + safeMid(postpatch_text, char_count2); + break; + case DELETE: + patch.length1 += aDiff.text.length(); + patch.diffs.append(aDiff); + postpatch_text = postpatch_text.left(char_count2) + + safeMid(postpatch_text, char_count2 + aDiff.text.length()); + break; + case EQUAL: + if (aDiff.text.length() <= 2 * Patch_Margin + && !patch.diffs.isEmpty() && !(aDiff == diffs.back())) { + // Small equality inside a patch. + patch.diffs.append(aDiff); + patch.length1 += aDiff.text.length(); + patch.length2 += aDiff.text.length(); + } + + if (aDiff.text.length() >= 2 * Patch_Margin) { + // Time for a new patch. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.append(patch); + patch = Patch(); + // Unlike Unidiff, our patch lists have a rolling context. + // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff + // Update prepatch text & pos to reflect the application of the + // just completed patch. + prepatch_text = postpatch_text; + char_count1 = char_count2; + } + } + break; + } + + // Update the current character count. + if (aDiff.operation != INSERT) { + char_count1 += aDiff.text.length(); + } + if (aDiff.operation != DELETE) { + char_count2 += aDiff.text.length(); + } + } + // Pick up the leftover patch if not empty. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.append(patch); + } + + return patches; +} + + +QList diff_match_patch::patch_deepCopy(QList &patches) { + QList patchesCopy; + foreach(Patch aPatch, patches) { + Patch patchCopy = Patch(); + foreach(Diff aDiff, aPatch.diffs) { + Diff diffCopy = Diff(aDiff.operation, aDiff.text); + patchCopy.diffs.append(diffCopy); + } + patchCopy.start1 = aPatch.start1; + patchCopy.start2 = aPatch.start2; + patchCopy.length1 = aPatch.length1; + patchCopy.length2 = aPatch.length2; + patchesCopy.append(patchCopy); + } + return patchesCopy; +} + + +QPair > diff_match_patch::patch_apply( + QList &patches, const QString &sourceText) { + QString text = sourceText; // Copy to preserve original. + if (patches.isEmpty()) { + return QPair >(text, QVector(0)); + } + + // Deep copy the patches so that no changes are made to originals. + QList patchesCopy = patch_deepCopy(patches); + + QString nullPadding = patch_addPadding(patchesCopy); + text = nullPadding + text + nullPadding; + patch_splitMax(patchesCopy); + + int x = 0; + // delta keeps track of the offset between the expected and actual location + // of the previous patch. If there are patches expected at positions 10 and + // 20, but the first patch was found at 12, delta is 2 and the second patch + // has an effective expected position of 22. + int delta = 0; + QVector results(patchesCopy.size()); + foreach(Patch aPatch, patchesCopy) { + int expected_loc = aPatch.start2 + delta; + QString text1 = diff_text1(aPatch.diffs); + int start_loc; + int end_loc = -1; + if (text1.length() > Match_MaxBits) { + // patch_splitMax will only provide an oversized pattern in the case of + // a monster delete. + start_loc = match_main(text, text1.left(Match_MaxBits), expected_loc); + if (start_loc != -1) { + end_loc = match_main(text, text1.right(Match_MaxBits), + expected_loc + text1.length() - Match_MaxBits); + if (end_loc == -1 || start_loc >= end_loc) { + // Can't find valid trailing context. Drop this patch. + start_loc = -1; + } + } + } else { + start_loc = match_main(text, text1, expected_loc); + } + if (start_loc == -1) { + // No match found. :( + results[x] = false; + // Subtract the delta for this failed patch from subsequent patches. + delta -= aPatch.length2 - aPatch.length1; + } else { + // Found a match. :) + results[x] = true; + delta = start_loc - expected_loc; + QString text2; + if (end_loc == -1) { + text2 = safeMid(text, start_loc, text1.length()); + } else { + text2 = safeMid(text, start_loc, end_loc + Match_MaxBits - start_loc); + } + if (text1 == text2) { + // Perfect match, just shove the replacement text in. + text = text.left(start_loc) + diff_text2(aPatch.diffs) + + safeMid(text, start_loc + text1.length()); + } else { + // Imperfect match. Run a diff to get a framework of equivalent + // indices. + QList diffs = diff_main(text1, text2, false); + if (text1.length() > Match_MaxBits + && diff_levenshtein(diffs) / static_cast (text1.length()) + > Patch_DeleteThreshold) { + // The end points match, but the content is unacceptably bad. + results[x] = false; + } else { + diff_cleanupSemanticLossless(diffs); + int index1 = 0; + foreach(Diff aDiff, aPatch.diffs) { + if (aDiff.operation != EQUAL) { + int index2 = diff_xIndex(diffs, index1); + if (aDiff.operation == INSERT) { + // Insertion + text = text.left(start_loc + index2) + aDiff.text + + safeMid(text, start_loc + index2); + } else if (aDiff.operation == DELETE) { + // Deletion + text = text.left(start_loc + index2) + + safeMid(text, start_loc + diff_xIndex(diffs, + index1 + aDiff.text.length())); + } + } + if (aDiff.operation != DELETE) { + index1 += aDiff.text.length(); + } + } + } + } + } + x++; + } + // Strip the padding off. + text = safeMid(text, nullPadding.length(), text.length() + - 2 * nullPadding.length()); + return QPair >(text, results); +} + + +QString diff_match_patch::patch_addPadding(QList &patches) { + short paddingLength = Patch_Margin; + QString nullPadding = ""; + for (short x = 1; x <= paddingLength; x++) { + nullPadding += QChar((ushort)x); + } + + // Bump all the patches forward. + QMutableListIterator pointer(patches); + while (pointer.hasNext()) { + Patch &aPatch = pointer.next(); + aPatch.start1 += paddingLength; + aPatch.start2 += paddingLength; + } + + // Add some padding on start of first diff. + Patch &firstPatch = patches.first(); + QList &firstPatchDiffs = firstPatch.diffs; + if (firstPatchDiffs.empty() || firstPatchDiffs.first().operation != EQUAL) { + // Add nullPadding equality. + firstPatchDiffs.prepend(Diff(EQUAL, nullPadding)); + firstPatch.start1 -= paddingLength; // Should be 0. + firstPatch.start2 -= paddingLength; // Should be 0. + firstPatch.length1 += paddingLength; + firstPatch.length2 += paddingLength; + } else if (paddingLength > firstPatchDiffs.first().text.length()) { + // Grow first equality. + Diff &firstDiff = firstPatchDiffs.first(); + int extraLength = paddingLength - firstDiff.text.length(); + firstDiff.text = safeMid(nullPadding, firstDiff.text.length(), + paddingLength - firstDiff.text.length()) + firstDiff.text; + firstPatch.start1 -= extraLength; + firstPatch.start2 -= extraLength; + firstPatch.length1 += extraLength; + firstPatch.length2 += extraLength; + } + + // Add some padding on end of last diff. + Patch &lastPatch = patches.first(); + QList &lastPatchDiffs = lastPatch.diffs; + if (lastPatchDiffs.empty() || lastPatchDiffs.last().operation != EQUAL) { + // Add nullPadding equality. + lastPatchDiffs.append(Diff(EQUAL, nullPadding)); + lastPatch.length1 += paddingLength; + lastPatch.length2 += paddingLength; + } else if (paddingLength > lastPatchDiffs.last().text.length()) { + // Grow last equality. + Diff &lastDiff = lastPatchDiffs.last(); + int extraLength = paddingLength - lastDiff.text.length(); + lastDiff.text += nullPadding.left(extraLength); + lastPatch.length1 += extraLength; + lastPatch.length2 += extraLength; + } + + return nullPadding; +} + + +void diff_match_patch::patch_splitMax(QList &patches) { + short patch_size = Match_MaxBits; + QString precontext, postcontext; + Patch patch; + int start1, start2; + bool empty; + Operation diff_type; + QString diff_text; + QMutableListIterator pointer(patches); + Patch bigpatch; + + if (pointer.hasNext()) { + bigpatch = pointer.next(); + } + + while (!bigpatch.isNull()) { + if (bigpatch.length1 <= patch_size) { + bigpatch = pointer.hasNext() ? pointer.next() : Patch(); + continue; + } + // Remove the big old patch. + pointer.remove(); + start1 = bigpatch.start1; + start2 = bigpatch.start2; + precontext = ""; + while (!bigpatch.diffs.isEmpty()) { + // Create one of several smaller patches. + patch = Patch(); + empty = true; + patch.start1 = start1 - precontext.length(); + patch.start2 = start2 - precontext.length(); + if (!precontext.isEmpty()) { + patch.length1 = patch.length2 = precontext.length(); + patch.diffs.append(Diff(EQUAL, precontext)); + } + while (!bigpatch.diffs.isEmpty() + && patch.length1 < patch_size - Patch_Margin) { + diff_type = bigpatch.diffs.front().operation; + diff_text = bigpatch.diffs.front().text; + if (diff_type == INSERT) { + // Insertions are harmless. + patch.length2 += diff_text.length(); + start2 += diff_text.length(); + patch.diffs.append(bigpatch.diffs.front()); + bigpatch.diffs.removeFirst(); + empty = false; + } else if (diff_type == DELETE && patch.diffs.size() == 1 + && patch.diffs.front().operation == EQUAL + && diff_text.length() > 2 * patch_size) { + // This is a large deletion. Let it pass in one chunk. + patch.length1 += diff_text.length(); + start1 += diff_text.length(); + empty = false; + patch.diffs.append(Diff(diff_type, diff_text)); + bigpatch.diffs.removeFirst(); + } else { + // Deletion or equality. Only take as much as we can stomach. + diff_text = diff_text.left(std::min(diff_text.length(), + patch_size - patch.length1 - Patch_Margin)); + patch.length1 += diff_text.length(); + start1 += diff_text.length(); + if (diff_type == EQUAL) { + patch.length2 += diff_text.length(); + start2 += diff_text.length(); + } else { + empty = false; + } + patch.diffs.append(Diff(diff_type, diff_text)); + if (diff_text == bigpatch.diffs.front().text) { + bigpatch.diffs.removeFirst(); + } else { + bigpatch.diffs.front().text = safeMid(bigpatch.diffs.front().text, + diff_text.length()); + } + } + } + // Compute the head context for the next patch. + precontext = diff_text2(patch.diffs); + precontext = safeMid(precontext, precontext.length() - Patch_Margin); + // Append the end context for this patch. + if (diff_text1(bigpatch.diffs).length() > Patch_Margin) { + postcontext = diff_text1(bigpatch.diffs).left(Patch_Margin); + } else { + postcontext = diff_text1(bigpatch.diffs); + } + if (!postcontext.isEmpty()) { + patch.length1 += postcontext.length(); + patch.length2 += postcontext.length(); + if (!patch.diffs.isEmpty() + && patch.diffs.back().operation == EQUAL) { + patch.diffs.back().text += postcontext; + } else { + patch.diffs.append(Diff(EQUAL, postcontext)); + } + } + if (!empty) { + pointer.insert(patch); + } + } + bigpatch = pointer.hasNext() ? pointer.next() : Patch(); + } +} + + +QString diff_match_patch::patch_toText(const QList &patches) { + QString text; + foreach(Patch aPatch, patches) { + text.append(aPatch.toString()); + } + return text; +} + + +QList diff_match_patch::patch_fromText(const QString &textline) { + QList patches; + if (textline.isEmpty()) { + return patches; + } + QStringList text = textline.split("\n", QString::SkipEmptyParts); + Patch patch; + QRegExp patchHeader("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); + char sign; + QString line; + while (!text.isEmpty()) { + if (!patchHeader.exactMatch(text.front())) { + throw QString("Invalid patch string: %1").arg(text.front()); + } + + patch = Patch(); + patch.start1 = patchHeader.cap(1).toInt(); + if (patchHeader.cap(2).isEmpty()) { + patch.start1--; + patch.length1 = 1; + } else if (patchHeader.cap(2) == "0") { + patch.length1 = 0; + } else { + patch.start1--; + patch.length1 = patchHeader.cap(2).toInt(); + } + + patch.start2 = patchHeader.cap(3).toInt(); + if (patchHeader.cap(4).isEmpty()) { + patch.start2--; + patch.length2 = 1; + } else if (patchHeader.cap(4) == "0") { + patch.length2 = 0; + } else { + patch.start2--; + patch.length2 = patchHeader.cap(4).toInt(); + } + text.removeFirst(); + + while (!text.isEmpty()) { + if (text.front().isEmpty()) { + text.removeFirst(); + continue; + } + sign = text.front()[0].toLatin1(); + line = safeMid(text.front(), 1); + line = line.replace("+", "%2B"); // decode would change all "+" to " " + line = QUrl::fromPercentEncoding(qPrintable(line)); + if (sign == '-') { + // Deletion. + patch.diffs.append(Diff(DELETE, line)); + } else if (sign == '+') { + // Insertion. + patch.diffs.append(Diff(INSERT, line)); + } else if (sign == ' ') { + // Minor equality. + patch.diffs.append(Diff(EQUAL, line)); + } else if (sign == '@') { + // Start of next patch. + break; + } else { + // WTF? + throw QString("Invalid patch mode '%1' in: %2").arg(sign).arg(line); + return QList(); + } + text.removeFirst(); + } + + patches.append(patch); + + } + return patches; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.h b/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.h new file mode 100644 index 0000000..d2eb0e8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.h @@ -0,0 +1,631 @@ +/* + * Copyright 2008 Google Inc. All Rights Reserved. + * Author: fraser@google.com (Neil Fraser) + * Author: mikeslemmer@gmail.com (Mike Slemmer) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Diff Match and Patch + * http://code.google.com/p/google-diff-match-patch/ + */ + +#ifndef DIFF_MATCH_PATCH_H +#define DIFF_MATCH_PATCH_H + +#include +#include "coreSQLiteStudio_global.h" + +/* + * Functions for diff, match and patch. + * Computes the difference between two texts to create a patch. + * Applies the patch onto another text, allowing for errors. + * + * @author fraser@google.com (Neil Fraser) + * + * Qt/C++ port by mikeslemmer@gmail.com (Mike Slemmer): + * + * Code known to compile and run with Qt 4.3 through Qt 4.7. + * + * Here is a trivial sample program which works properly when linked with this + * library: + * + + #include + #include + #include + #include + #include + #include "diff_match_patch.h" + int main(int argc, char **argv) { + diff_match_patch dmp; + QString str1 = QString("First string in diff"); + QString str2 = QString("Second string in diff"); + + QString strPatch = dmp.patch_toText(dmp.patch_make(str1, str2)); + QPair > out + = dmp.patch_apply(dmp.patch_fromText(strPatch), str1); + QString strResult = out.first; + + // here, strResult will equal str2 above. + return 0; + } + + */ + + +/**- +* The data structure representing a diff is a Linked list of Diff objects: +* {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"), +* Diff(Operation.EQUAL, " world.")} +* which means: delete "Hello", add "Goodbye" and keep " world." +*/ +enum Operation { + DELETE, INSERT, EQUAL +}; + + +/** +* Class representing one diff operation. +*/ +class API_EXPORT Diff { + public: + Operation operation; + // One of: INSERT, DELETE or EQUAL. + QString text; + // The text associated with this diff operation. + + /** + * Constructor. Initializes the diff with the provided values. + * @param operation One of INSERT, DELETE or EQUAL. + * @param text The text being applied. + */ + Diff(Operation _operation, const QString &_text); + Diff(); + inline bool isNull() const; + QString toString() const; + bool operator==(const Diff &d) const; + bool operator!=(const Diff &d) const; + + static QString strOperation(Operation op); +}; + + +/** +* Class representing one patch operation. +*/ +class API_EXPORT Patch { + public: + QList diffs; + int start1; + int start2; + int length1; + int length2; + + /** + * Constructor. Initializes with an empty list of diffs. + */ + Patch(); + bool isNull() const; + QString toString(); +}; + + +/** + * Class containing the diff, match and patch methods. + * Also contains the behaviour settings. + */ +class API_EXPORT diff_match_patch { + + friend class diff_match_patch_test; + + public: + // Defaults. + // Set these on your diff_match_patch instance to override the defaults. + + // Number of seconds to map a diff before giving up (0 for infinity). + float Diff_Timeout; + // Cost of an empty edit operation in terms of edit characters. + short Diff_EditCost; + // At what point is no match declared (0.0 = perfection, 1.0 = very loose). + float Match_Threshold; + // How far to search for a match (0 = exact location, 1000+ = broad match). + // A match this many characters away from the expected location will add + // 1.0 to the score (0.0 is a perfect match). + int Match_Distance; + // When deleting a large block of text (over ~64 characters), how close does + // the contents have to match the expected contents. (0.0 = perfection, + // 1.0 = very loose). Note that Match_Threshold controls how closely the + // end points of a delete need to match. + float Patch_DeleteThreshold; + // Chunk size for context length. + short Patch_Margin; + + // The number of bits in an int. + short Match_MaxBits; + + private: + // Define some regex patterns for matching boundaries. + static QRegExp BLANKLINEEND; + static QRegExp BLANKLINESTART; + + + public: + + diff_match_patch(); + + // DIFF FUNCTIONS + + + /** + * Find the differences between two texts. + * Run a faster slightly less optimal diff. + * This method allows the 'checklines' of diff_main() to be optional. + * Most of the time checklines is wanted, so default to true. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @return Linked List of Diff objects. + */ + QList diff_main(const QString &text1, const QString &text2); + + /** + * Find the differences between two texts. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @return Linked List of Diff objects. + */ + QList diff_main(const QString &text1, const QString &text2, bool checklines); + + /** + * Find the differences between two texts. Simplifies the problem by + * stripping any common prefix or suffix off the texts before diffing. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @param deadline Time when the diff should be complete by. Used + * internally for recursive calls. Users should set DiffTimeout instead. + * @return Linked List of Diff objects. + */ + private: + QList diff_main(const QString &text1, const QString &text2, bool checklines, clock_t deadline); + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @param deadline Time when the diff should be complete by. + * @return Linked List of Diff objects. + */ + private: + QList diff_compute(QString text1, QString text2, bool checklines, clock_t deadline); + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time when the diff should be complete by. + * @return Linked List of Diff objects. + */ + private: + QList diff_lineMode(QString text1, QString text2, clock_t deadline); + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @return Linked List of Diff objects. + */ + protected: + QList diff_bisect(const QString &text1, const QString &text2, clock_t deadline); + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param x Index of split point in text1. + * @param y Index of split point in text2. + * @param deadline Time at which to bail if not yet complete. + * @return LinkedList of Diff objects. + */ + private: + QList diff_bisectSplit(const QString &text1, const QString &text2, int x, int y, clock_t deadline); + + /** + * Split two texts into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text1 First string. + * @param text2 Second string. + * @return Three element Object array, containing the encoded text1, the + * encoded text2 and the List of unique strings. The zeroth element + * of the List of unique strings is intentionally blank. + */ + protected: + QList diff_linesToChars(const QString &text1, const QString &text2); // return elems 0 and 1 are QString, elem 2 is QStringList + + /** + * Split a text into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text String to encode. + * @param lineArray List of unique strings. + * @param lineHash Map of strings to indices. + * @return Encoded string. + */ + private: + QString diff_linesToCharsMunge(const QString &text, QStringList &lineArray, + QMap &lineHash); + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param diffs LinkedList of Diff objects. + * @param lineArray List of unique strings. + */ + private: + void diff_charsToLines(QList &diffs, const QStringList &lineArray); + + /** + * Determine the common prefix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the start of each string. + */ + public: + int diff_commonPrefix(const QString &text1, const QString &text2); + + /** + * Determine the common suffix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of each string. + */ + public: + int diff_commonSuffix(const QString &text1, const QString &text2); + + /** + * Determine if the suffix of one string is the prefix of another. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of the first + * string and the start of the second string. + */ + protected: + int diff_commonOverlap(const QString &text1, const QString &text2); + + /** + * Do the two texts share a substring which is at least half the length of + * the longer text? + * This speedup can produce non-minimal diffs. + * @param text1 First string. + * @param text2 Second string. + * @return Five element String array, containing the prefix of text1, the + * suffix of text1, the prefix of text2, the suffix of text2 and the + * common middle. Or null if there was no match. + */ + protected: + QStringList diff_halfMatch(const QString &text1, const QString &text2); + + /** + * Does a substring of shorttext exist within longtext such that the + * substring is at least half the length of longtext? + * @param longtext Longer string. + * @param shorttext Shorter string. + * @param i Start index of quarter length substring within longtext. + * @return Five element String array, containing the prefix of longtext, the + * suffix of longtext, the prefix of shorttext, the suffix of shorttext + * and the common middle. Or null if there was no match. + */ + private: + QStringList diff_halfMatchI(const QString &longtext, const QString &shorttext, int i); + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param diffs LinkedList of Diff objects. + */ + public: + void diff_cleanupSemantic(QList &diffs); + + /** + * Look for single edits surrounded on both sides by equalities + * which can be shifted sideways to align the edit to a word boundary. + * e.g: The cat came. -> The cat came. + * @param diffs LinkedList of Diff objects. + */ + public: + void diff_cleanupSemanticLossless(QList &diffs); + + /** + * Given two strings, compute a score representing whether the internal + * boundary falls on logical boundaries. + * Scores range from 6 (best) to 0 (worst). + * @param one First string. + * @param two Second string. + * @return The score. + */ + private: + int diff_cleanupSemanticScore(const QString &one, const QString &two); + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param diffs LinkedList of Diff objects. + */ + public: + void diff_cleanupEfficiency(QList &diffs); + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param diffs LinkedList of Diff objects. + */ + public: + void diff_cleanupMerge(QList &diffs); + + /** + * loc is a location in text1, compute and return the equivalent location in + * text2. + * e.g. "The cat" vs "The big cat", 1->1, 5->8 + * @param diffs LinkedList of Diff objects. + * @param loc Location within text1. + * @return Location within text2. + */ + public: + int diff_xIndex(const QList &diffs, int loc); + + /** + * Convert a Diff list into a pretty HTML report. + * @param diffs LinkedList of Diff objects. + * @return HTML representation. + */ + public: + QString diff_prettyHtml(const QList &diffs); + + /** + * Compute and return the source text (all equalities and deletions). + * @param diffs LinkedList of Diff objects. + * @return Source text. + */ + public: + QString diff_text1(const QList &diffs); + + /** + * Compute and return the destination text (all equalities and insertions). + * @param diffs LinkedList of Diff objects. + * @return Destination text. + */ + public: + QString diff_text2(const QList &diffs); + + /** + * Compute the Levenshtein distance; the number of inserted, deleted or + * substituted characters. + * @param diffs LinkedList of Diff objects. + * @return Number of changes. + */ + public: + int diff_levenshtein(const QList &diffs); + + /** + * Crush the diff into an encoded string which describes the operations + * required to transform text1 into text2. + * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. + * Operations are tab-separated. Inserted text is escaped using %xx notation. + * @param diffs Array of diff tuples. + * @return Delta text. + */ + public: + QString diff_toDelta(const QList &diffs); + + /** + * Given the original text1, and an encoded string which describes the + * operations required to transform text1 into text2, compute the full diff. + * @param text1 Source string for the diff. + * @param delta Delta text. + * @return Array of diff tuples or null if invalid. + * @throws QString If invalid input. + */ + public: + QList diff_fromDelta(const QString &text1, const QString &delta); + + + // MATCH FUNCTIONS + + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc'. + * Returns -1 if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + public: + int match_main(const QString &text, const QString &pattern, int loc); + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc' using the + * Bitap algorithm. Returns -1 if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + protected: + int match_bitap(const QString &text, const QString &pattern, int loc); + + /** + * Compute and return the score for a match with e errors and x location. + * @param e Number of errors in match. + * @param x Location of match. + * @param loc Expected location of match. + * @param pattern Pattern being sought. + * @return Overall score for match (0.0 = good, 1.0 = bad). + */ + private: + double match_bitapScore(int e, int x, int loc, const QString &pattern); + + /** + * Initialise the alphabet for the Bitap algorithm. + * @param pattern The text to encode. + * @return Hash of character locations. + */ + protected: + QMap match_alphabet(const QString &pattern); + + + // PATCH FUNCTIONS + + + /** + * Increase the context until it is unique, + * but don't let the pattern expand beyond Match_MaxBits. + * @param patch The patch to grow. + * @param text Source text. + */ + protected: + void patch_addContext(Patch &patch, const QString &text); + + /** + * Compute a list of patches to turn text1 into text2. + * A set of diffs will be computed. + * @param text1 Old text. + * @param text2 New text. + * @return LinkedList of Patch objects. + */ + public: + QList patch_make(const QString &text1, const QString &text2); + + /** + * Compute a list of patches to turn text1 into text2. + * text1 will be derived from the provided diffs. + * @param diffs Array of diff tuples for text1 to text2. + * @return LinkedList of Patch objects. + */ + public: + QList patch_make(const QList &diffs); + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is ignored, diffs are the delta between text1 and text2. + * @param text1 Old text. + * @param text2 Ignored. + * @param diffs Array of diff tuples for text1 to text2. + * @return LinkedList of Patch objects. + * @deprecated Prefer patch_make(const QString &text1, const QList &diffs). + */ + public: + QList patch_make(const QString &text1, const QString &text2, const QList &diffs); + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is not provided, diffs are the delta between text1 and text2. + * @param text1 Old text. + * @param diffs Array of diff tuples for text1 to text2. + * @return LinkedList of Patch objects. + */ + public: + QList patch_make(const QString &text1, const QList &diffs); + + /** + * Given an array of patches, return another array that is identical. + * @param patches Array of patch objects. + * @return Array of patch objects. + */ + public: + QList patch_deepCopy(QList &patches); + + /** + * Merge a set of patches onto the text. Return a patched text, as well + * as an array of true/false values indicating which patches were applied. + * @param patches Array of patch objects. + * @param text Old text. + * @return Two element Object array, containing the new text and an array of + * boolean values. + */ + public: + QPair > patch_apply(QList &patches, const QString &text); + + /** + * Add some padding on text start and end so that edges can match something. + * Intended to be called only from within patch_apply. + * @param patches Array of patch objects. + * @return The padding string added to each side. + */ + public: + QString patch_addPadding(QList &patches); + + /** + * Look through the patches and break up any which are longer than the + * maximum limit of the match algorithm. + * Intended to be called only from within patch_apply. + * @param patches LinkedList of Patch objects. + */ + public: + void patch_splitMax(QList &patches); + + /** + * Take a list of patches and return a textual representation. + * @param patches List of Patch objects. + * @return Text representation of patches. + */ + public: + QString patch_toText(const QList &patches); + + /** + * Parse a textual representation of patches and return a List of Patch + * objects. + * @param textline Text representation of patches. + * @return List of Patch objects. + * @throws QString If invalid input. + */ + public: + QList patch_fromText(const QString &textline); + + /** + * A safer version of QString.mid(pos). This one returns "" instead of + * null when the postion equals the string length. + * @param str String to take a substring from. + * @param pos Position to start the substring from. + * @return Substring. + */ + private: + static inline QString safeMid(const QString &str, int pos) { + return (pos == str.length()) ? QString("") : str.mid(pos); + } + + /** + * A safer version of QString.mid(pos, len). This one returns "" instead of + * null when the postion equals the string length. + * @param str String to take a substring from. + * @param pos Position to start the substring from. + * @param len Length of substring. + * @return Substring. + */ + private: + static inline QString safeMid(const QString &str, int pos, int len) { + return (pos == str.length()) ? QString("") : str.mid(pos, len); + } +}; + +#endif // DIFF_MATCH_PATCH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp b/SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp new file mode 100644 index 0000000..a02f8c9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp @@ -0,0 +1,73 @@ +#include "expectedtoken.h" + +bool ExpectedToken::needsWrapping() const +{ + switch (type) + { + case ExpectedToken::COLUMN: + case ExpectedToken::TABLE: + case ExpectedToken::INDEX: + case ExpectedToken::TRIGGER: + case ExpectedToken::VIEW: + case ExpectedToken::DATABASE: + case ExpectedToken::OTHER: + case ExpectedToken::COLLATION: + return true; + case ExpectedToken::KEYWORD: + case ExpectedToken::FUNCTION: + case ExpectedToken::OPERATOR: + case ExpectedToken::STRING: + case ExpectedToken::NUMBER: + case ExpectedToken::BLOB: + case ExpectedToken::PRAGMA: + case ExpectedToken::NO_VALUE: + return false; + } + return false; +} + +int ExpectedToken::operator==(const ExpectedToken& other) +{ + return type == other.type && value == other.value && contextInfo == other.contextInfo && + label == other.label && prefix == other.prefix; +} + +QString ExpectedToken::toString() const +{ + return QString("%4. %1 : %2 (ctx: %3) [label: %5]").arg(value).arg(type).arg(contextInfo).arg(prefix).arg(label); +} + +ExpectedTokenPtr::ExpectedTokenPtr() : + QSharedPointer() +{ +} + +ExpectedTokenPtr::ExpectedTokenPtr(ExpectedToken* ptr) : + QSharedPointer(ptr) +{ +} + +ExpectedTokenPtr::ExpectedTokenPtr(const QSharedPointer& other) : + QSharedPointer(other) +{ +} + +ExpectedTokenPtr::ExpectedTokenPtr(const QWeakPointer& other) : + QSharedPointer(other) +{ +} + +int operator==(const ExpectedTokenPtr& ptr1, const ExpectedTokenPtr& ptr2) +{ + return *ptr1.data() == *ptr2.data(); +} + +int qHash(const ExpectedToken& token) +{ + return token.type ^ qHash(token.value + "/" + token.value + "/" + token.contextInfo + "/" + token.label + "/" + token.prefix); +} + +int qHash(const ExpectedTokenPtr& ptr) +{ + return qHash(*ptr.data()); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/expectedtoken.h b/SQLiteStudio3/coreSQLiteStudio/expectedtoken.h new file mode 100644 index 0000000..9242dd8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/expectedtoken.h @@ -0,0 +1,107 @@ +#ifndef EXPECTEDTOKEN_H +#define EXPECTEDTOKEN_H + +#include "coreSQLiteStudio_global.h" +#include +#include + +struct API_EXPORT ExpectedToken +{ + /** + * @brief The expected token type + * Order of this enum matters, because it defines the order + * of sorting a list of expected tokens by CompletionComparer. + * The NO_VALUE value means that there is no prepared value, + * but some context information and/or label is available. + */ + enum Type + { + COLUMN, + TABLE, + INDEX, + TRIGGER, + VIEW, + DATABASE, + NO_VALUE, + KEYWORD, + FUNCTION, + OPERATOR, + COLLATION, + PRAGMA, + STRING, + NUMBER, + BLOB, + OTHER + }; + + /** + * @brief type Token type. + */ + Type type; + + /** + * @brief value Token value. + * The actual value to be inserted in expected position. + */ + QString value; + + /** + * @brief contextInfo Context information. + * Context of proposed token. Context is for example table, that proposed column belongs to, + * or it's database, that proposed table belongs to. This is used when completer sorts results. + */ + QString contextInfo; + + /** + * @brief label Token explanation label. + * This is a context info to be displayed in completer. It may provide info about actual object under the alias, + * or human readable info about the proposed token (like "New column name"). + */ + QString label; + + /** + * @brief prefix Token prefix. + * Necessary prefix for token to be valid. It should be concatenated with the value using a dot character. + */ + QString prefix; + + /** + * @brief Token priority on completion list. + * + * Some tokens require manual control on where they appear in the completion list. If this value is any positive + * number, than it's considered as a priority. The higher value, the higher priority. 0 and negative numbers are ignored. + * The priority has a precedence before any other sorting rules. + */ + int priority = 0; + + /** + * @brief needsWrapping Tells if the token requires wrapping. + * @return true if the token is of type that might require wrapping (in case the value contains forbidden characters or keyword as a name). + */ + bool needsWrapping() const; + + int operator==(const ExpectedToken& other); + QString toString() const; +}; + +/** + * @brief The ExpectedTokenPtr class + * This class would normally be just a typedef, but we need qHash() functionality + * being applied for this type, so simple typedef wouldn't overwrite the type matching for qHash() arguments. + * The qHash() is necessary for QSet and we need that for getting rid of duplicates. + */ +class API_EXPORT ExpectedTokenPtr : public QSharedPointer +{ + public: + ExpectedTokenPtr(); + explicit ExpectedTokenPtr(ExpectedToken* ptr); + explicit ExpectedTokenPtr(const QSharedPointer & other); + explicit ExpectedTokenPtr(const QWeakPointer & other); +}; + +int operator==(const ExpectedTokenPtr& ptr1, const ExpectedTokenPtr& ptr2); + +int qHash(const ExpectedToken& token); +int qHash(const ExpectedTokenPtr &ptr); + +#endif // EXPECTEDTOKEN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/exportworker.cpp b/SQLiteStudio3/coreSQLiteStudio/exportworker.cpp new file mode 100644 index 0000000..9d227de --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/exportworker.cpp @@ -0,0 +1,516 @@ +#include "exportworker.h" +#include "plugins/exportplugin.h" +#include "schemaresolver.h" +#include "services/notifymanager.h" +#include "common/utils_sql.h" +#include "common/utils.h" +#include +#include + +ExportWorker::ExportWorker(ExportPlugin* plugin, ExportManager::StandardExportConfig* config, QIODevice* output, QObject *parent) : + QObject(parent), plugin(plugin), config(config), output(output) +{ + executor = new QueryExecutor(); + executor->setAsyncMode(false); // worker runs in its own thread, we don't need (and we don't want) async executor + executor->setNoMetaColumns(true); // we don't want rowid columns, we want only columns requested by the original query +} + +ExportWorker::~ExportWorker() +{ + safe_delete(executor); + safe_delete(parser); +} + +void ExportWorker::run() +{ + bool res = false; + switch (exportMode) + { + case ExportManager::QUERY_RESULTS: + { + res = exportQueryResults(); + break; + } + case ExportManager::DATABASE: + { + res = exportDatabase(); + break; + } + case ExportManager::TABLE: + { + res = exportTable(); + break; + } + case ExportManager::UNDEFINED: + qCritical() << "Started ExportWorker with UNDEFINED mode."; + res = false; + break; + case ExportManager::FILE: + case ExportManager::CLIPBOARD: + break; + } + + plugin->cleanupAfterExport(); + + emit finished(res, output); +} + +void ExportWorker::prepareExportQueryResults(Db* db, const QString& query) +{ + this->db = db; + this->query = query; + exportMode = ExportManager::QUERY_RESULTS; + prepareParser(); +} + +void ExportWorker::prepareExportDatabase(Db* db, const QStringList& objectListToExport) +{ + this->db = db; + this->objectListToExport = objectListToExport; + exportMode = ExportManager::DATABASE; + prepareParser(); +} + +void ExportWorker::prepareExportTable(Db* db, const QString& database, const QString& table) +{ + this->db = db; + this->database = database; + this->table = table; + exportMode = ExportManager::TABLE; + prepareParser(); +} + +void ExportWorker::prepareParser() +{ + safe_delete(parser); + parser = new Parser(db->getDialect()); +} + +void ExportWorker::interrupt() +{ + QMutexLocker locker(&interruptMutex); + interrupted = true; + if (executor->isExecutionInProgress()) + executor->interrupt(); +} + +bool ExportWorker::exportQueryResults() +{ + executor->setDb(db); + executor->exec(query); + SqlQueryPtr results = executor->getResults(); + if (!results) + { + qCritical() << "Null results from executor in ExportWorker."; + return false; + } + + QList resultColumns = executor->getResultColumns(); + QHash providerData = getProviderDataForQueryResults(); + + if (results->isInterrupted()) + return false; + + if (results->isError()) + { + notifyError(tr("Error while exporting query results: %1").arg(results->getErrorText())); + return false; + } + + if (!plugin->initBeforeExport(db, output, *config)) + return false; + + if (!plugin->beforeExportQueryResults(query, resultColumns, providerData)) + return false; + + if (isInterrupted()) + return false; + + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + if (!plugin->exportQueryResultsRow(row)) + return false; + + if (isInterrupted()) + return false; + } + + if (!plugin->afterExportQueryResults()) + return false; + + if (!plugin->afterExport()) + return false; + + return true; +} + +QHash ExportWorker::getProviderDataForQueryResults() +{ + static const QString colLengthSql = QStringLiteral("SELECT %1 FROM (%2)"); + static const QString colLengthTpl = QStringLiteral("max(length(%1))"); + QHash providerData; + + if (plugin->getProviderFlags().testFlag(ExportManager::ROW_COUNT)) + { + executor->countResults(); + providerData[ExportManager::ROW_COUNT] = executor->getTotalRowsReturned(); + } + + if (plugin->getProviderFlags().testFlag(ExportManager::DATA_LENGTHS)) + { + QStringList wrappedCols; + for (const QueryExecutor::ResultColumnPtr& col : executor->getResultColumns()) + wrappedCols << colLengthTpl.arg(wrapObjIfNeeded(col->displayName, db->getDialect())); + + executor->exec(colLengthSql.arg(wrappedCols.join(", "), query)); + SqlQueryPtr results = executor->getResults(); + if (!results) + { + qCritical() << "Null results from executor in ExportWorker."; + } + else if (results->isError()) + { + notifyError(tr("Error while counting data column width to export from query results: %2").arg(results->getErrorText())); + } + else + { + QList colWidths; + for (const QVariant& value : results->next()->valueList()) + colWidths << value.toInt(); + + providerData[ExportManager::DATA_LENGTHS] = QVariant::fromValue(colWidths); + } + } + return providerData; +} + +bool ExportWorker::exportDatabase() +{ + QString err; + QList dbObjects = collectDbObjects(&err); + if (!err.isNull()) + { + notifyError(err); + return false; + } + + if (!plugin->initBeforeExport(db, output, *config)) + return false; + + if (!plugin->beforeExportDatabase(db->getName())) + return false; + + if (isInterrupted()) + return false; + + QList order = { + ExportManager::ExportObject::TABLE, ExportManager::ExportObject::INDEX, ExportManager::ExportObject::TRIGGER, ExportManager::ExportObject::VIEW + }; + + qSort(dbObjects.begin(), dbObjects.end(), [=](const ExportManager::ExportObjectPtr& dbObj1, const ExportManager::ExportObjectPtr& dbObj2) -> bool + { + return order.indexOf(dbObj1->type) < order.indexOf(dbObj2->type); + }); + + if (!plugin->beforeExportTables()) + return false; + + if (!exportDatabaseObjects(dbObjects, ExportManager::ExportObject::TABLE)) + return false; + + if (!plugin->afterExportTables()) + return false; + + if (!plugin->beforeExportIndexes()) + return false; + + if (!exportDatabaseObjects(dbObjects, ExportManager::ExportObject::INDEX)) + return false; + + if (!plugin->afterExportIndexes()) + return false; + + if (!plugin->beforeExportTriggers()) + return false; + + if (!exportDatabaseObjects(dbObjects, ExportManager::ExportObject::TRIGGER)) + return false; + + if (!plugin->afterExportTriggers()) + return false; + + if (!plugin->beforeExportViews()) + return false; + + if (!exportDatabaseObjects(dbObjects, ExportManager::ExportObject::VIEW)) + return false; + + if (!plugin->afterExportViews()) + return false; + + if (!plugin->afterExportDatabase()) + return false; + + if (!plugin->afterExport()) + return false; + + return true; +} + +bool ExportWorker::exportDatabaseObjects(const QList& dbObjects, ExportManager::ExportObject::Type type) +{ + SqliteQueryPtr parsedQuery; + bool res = true; + for (const ExportManager::ExportObjectPtr& obj : dbObjects) + { + if (obj->type != type) + continue; + + res = parser->parse(obj->ddl); + if (!res || parser->getQueries().size() < 1) + { + qCritical() << "Could not parse" << obj->name << ", the DDL was:" << obj->ddl << ", error is:" << parser->getErrorString(); + notifyWarn(tr("Could not parse %1 in order to export it. It will be excluded from the export output.").arg(obj->name)); + continue; + } + parsedQuery = parser->getQueries().first(); + + switch (obj->type) + { + case ExportManager::ExportObject::TABLE: + res = exportTableInternal(obj->database, obj->name, obj->ddl, parsedQuery, obj->data, obj->providerData); + break; + case ExportManager::ExportObject::INDEX: + res = plugin->exportIndex(obj->database, obj->name, obj->ddl, parsedQuery.dynamicCast()); + break; + case ExportManager::ExportObject::TRIGGER: + res = plugin->exportTrigger(obj->database, obj->name, obj->ddl, parsedQuery.dynamicCast()); + break; + case ExportManager::ExportObject::VIEW: + res = plugin->exportView(obj->database, obj->name, obj->ddl, parsedQuery.dynamicCast()); + break; + default: + qDebug() << "Unhandled ExportObject type:" << obj->type; + break; + } + + if (!res) + return false; + + if (isInterrupted()) + return false; + } + return true; +} + +bool ExportWorker::exportTable() +{ + SqlQueryPtr results; + QString errorMessage; + QHash providerData; + queryTableDataToExport(db, table, results, providerData, &errorMessage); + if (!errorMessage.isNull()) + { + notifyError(errorMessage); + return false; + } + + SchemaResolver resolver(db); + QString ddl = resolver.getObjectDdl(database, table, SchemaResolver::TABLE); + + if (!parser->parse(ddl) || parser->getQueries().size() < 1) + { + qCritical() << "Could not parse" << table << ", the DDL was:" << ddl << ", error is:" << parser->getErrorString(); + notifyWarn(tr("Could not parse %1 in order to export it. It will be excluded from the export output.").arg(table)); + return false; + } + + SqliteQueryPtr createTable = parser->getQueries().first(); + if (!plugin->initBeforeExport(db, output, *config)) + return false; + + if (!exportTableInternal(database, table, ddl, createTable, results, providerData)) + return false; + + if (config->exportTableIndexes) + { + if (!plugin->beforeExportIndexes()) + return false; + + QList parsedIndexesForTable = resolver.getParsedIndexesForTable(database, table); + for (const SqliteCreateIndexPtr& idx : parsedIndexesForTable) + { + if (!plugin->exportIndex(database, idx->index, idx->detokenize(), idx)) + return false; + } + + if (!plugin->afterExportIndexes()) + return false; + } + + if (config->exportTableTriggers) + { + if (!plugin->beforeExportTriggers()) + return false; + + QList parsedTriggersForTable = resolver.getParsedTriggersForTable(database, table); + for (const SqliteCreateTriggerPtr& trig : parsedTriggersForTable) + { + if (!plugin->exportTrigger(database, trig->trigger, trig->detokenize(), trig)) + return false; + } + + if (!plugin->afterExportTriggers()) + return false; + } + + if (!plugin->afterExport()) + return false; + + return true; +} + +bool ExportWorker::exportTableInternal(const QString& database, const QString& table, const QString& ddl, SqliteQueryPtr parsedDdl, SqlQueryPtr results, + const QHash& providerData) +{ + SqliteCreateTablePtr createTable = parsedDdl.dynamicCast(); + SqliteCreateVirtualTablePtr createVirtualTable = parsedDdl.dynamicCast(); + + if (createTable) + { + if (!plugin->exportTable(database, table, results->getColumnNames(), ddl, createTable, providerData)) + return false; + } + else + { + if (!plugin->exportVirtualTable(database, table, results->getColumnNames(), ddl, createVirtualTable, providerData)) + return false; + } + + if (isInterrupted()) + return false; + + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + if (!plugin->exportTableRow(row)) + return false; + + if (isInterrupted()) + return false; + } + + if (!plugin->afterExportTable()) + return false; + + return true; +} + +QList ExportWorker::collectDbObjects(QString* errorMessage) +{ + SchemaResolver resolver(db); + StrHash allDetails = resolver.getAllObjectDetails(); + + QList objectsToExport; + ExportManager::ExportObjectPtr exportObj; + SchemaResolver::ObjectDetails details; + for (const QString& objName : objectListToExport) + { + if (!allDetails.contains(objName)) + { + qWarning() << "Object requested for export, but not on list of db object details:" << objName; + continue; + } + details = allDetails[objName]; + + exportObj = ExportManager::ExportObjectPtr::create(); + if (details.type == SchemaResolver::TABLE) + { + exportObj->type = ExportManager::ExportObject::TABLE; + queryTableDataToExport(db, objName, exportObj->data, exportObj->providerData, errorMessage); + if (!errorMessage->isNull()) + return objectsToExport; + } + else if (details.type == SchemaResolver::INDEX) + exportObj->type = ExportManager::ExportObject::INDEX; + else if (details.type == SchemaResolver::TRIGGER) + exportObj->type = ExportManager::ExportObject::TRIGGER; + else if (details.type == SchemaResolver::VIEW) + exportObj->type = ExportManager::ExportObject::VIEW; + else + continue; // warning about this case is already in SchemaResolver + + exportObj->name = objName; + exportObj->ddl = details.ddl; + objectsToExport << exportObj; + } + + qSort(objectsToExport.begin(), objectsToExport.end(), [](ExportManager::ExportObjectPtr obj1, ExportManager::ExportObjectPtr obj2) -> bool + { + return (obj1->type < obj2->type); + }); + + return objectsToExport; +} + +void ExportWorker::queryTableDataToExport(Db* db, const QString& table, SqlQueryPtr& dataPtr, QHash& providerData, + QString* errorMessage) const +{ + static const QString sql = QStringLiteral("SELECT * FROM %1"); + static const QString countSql = QStringLiteral("SELECT count(*) FROM %1"); + static const QString colLengthSql = QStringLiteral("SELECT %1 FROM %2"); + static const QString colLengthTpl = QStringLiteral("max(length(%1))"); + + if (config->exportData) + { + QString wrappedTable = wrapObjIfNeeded(table, db->getDialect()); + + dataPtr = db->exec(sql.arg(wrappedTable)); + if (dataPtr->isError()) + *errorMessage = tr("Error while reading data to export from table %1: %2").arg(table, dataPtr->getErrorText()); + + if (plugin->getProviderFlags().testFlag(ExportManager::ROW_COUNT)) + { + SqlQueryPtr countQuery = db->exec(countSql.arg(wrappedTable)); + if (countQuery->isError()) + { + if (errorMessage->isNull()) + *errorMessage = tr("Error while counting data to export from table %1: %2").arg(table, dataPtr->getErrorText()); + } + else + providerData[ExportManager::ROW_COUNT] = countQuery->getSingleCell().toInt(); + } + + if (plugin->getProviderFlags().testFlag(ExportManager::DATA_LENGTHS)) + { + QStringList wrappedCols; + for (const QString& col : dataPtr->getColumnNames()) + wrappedCols << colLengthTpl.arg(wrapObjIfNeeded(col, db->getDialect())); + + SqlQueryPtr colLengthQuery = db->exec(colLengthSql.arg(wrappedCols.join(", "), wrappedTable)); + if (colLengthQuery->isError()) + { + if (errorMessage->isNull()) + *errorMessage = tr("Error while counting data column width to export from table %1: %2").arg(table, dataPtr->getErrorText()); + } + else + { + QList colWidths; + for (const QVariant& value : colLengthQuery->next()->valueList()) + colWidths << value.toInt(); + + providerData[ExportManager::DATA_LENGTHS] = QVariant::fromValue(colWidths); + } + } + } +} + +bool ExportWorker::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/exportworker.h b/SQLiteStudio3/coreSQLiteStudio/exportworker.h new file mode 100644 index 0000000..27620a4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/exportworker.h @@ -0,0 +1,60 @@ +#ifndef EXPORTWORKER_H +#define EXPORTWORKER_H + +#include "services/exportmanager.h" +#include "db/queryexecutor.h" +#include "parser/ast/sqlitecreatetable.h" +#include +#include +#include + +class Db; + +class API_EXPORT ExportWorker : public QObject, public QRunnable +{ + Q_OBJECT + public: + ExportWorker(ExportPlugin* plugin, ExportManager::StandardExportConfig* config, QIODevice* output, QObject *parent = 0); + ~ExportWorker(); + + void run(); + void prepareExportQueryResults(Db* db, const QString& query); + void prepareExportDatabase(Db* db, const QStringList& objectListToExport); + void prepareExportTable(Db* db, const QString& database, const QString& table); + + private: + void prepareParser(); + bool exportQueryResults(); + QHash getProviderDataForQueryResults(); + bool exportDatabase(); + bool exportDatabaseObjects(const QList& dbObjects, ExportManager::ExportObject::Type type); + bool exportTable(); + bool exportTableInternal(const QString& database, const QString& table, const QString& ddl, SqliteQueryPtr parsedDdl, SqlQueryPtr results, + const QHash& providerData); + QList collectDbObjects(QString* errorMessage); + void queryTableDataToExport(Db* db, const QString& table, SqlQueryPtr& dataPtr, QHash& providerData, + QString* errorMessage) const; + bool isInterrupted(); + + ExportPlugin* plugin = nullptr; + ExportManager::StandardExportConfig* config = nullptr; + QIODevice* output = nullptr; + ExportManager::ExportMode exportMode = ExportManager::UNDEFINED; + Db* db = nullptr; + QString query; + QString database; + QString table; + QStringList objectListToExport; + QueryExecutor* executor = nullptr; + bool interrupted = false; + QMutex interruptMutex; + Parser* parser = nullptr; + + public slots: + void interrupt(); + + signals: + void finished(bool result, QIODevice* output); +}; + +#endif // EXPORTWORKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.cpp new file mode 100644 index 0000000..75d94eb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.cpp @@ -0,0 +1,178 @@ +#include "dbattacherimpl.h" +#include "db/db.h" +#include "services/dbmanager.h" +#include "parser/parser.h" +#include "services/notifymanager.h" +#include "common/utils_sql.h" +#include + +DbAttacherImpl::DbAttacherImpl(Db* db) +{ + this->db = db; + dialect = db->getDialect(); +} + +bool DbAttacherImpl::attachDatabases(const QString& query) +{ + Parser parser(dialect); + if (!parser.parse(query)) + return false; + + queries = parser.getQueries(); + return attachDatabases(); +} + +bool DbAttacherImpl::attachDatabases(const QList& queries) +{ + this->queries = queries; + return attachDatabases(); +} + +bool DbAttacherImpl::attachDatabases(SqliteQueryPtr query) +{ + queries.clear(); + queries << query; + return attachDatabases(); +} + +void DbAttacherImpl::detachDatabases() +{ + detachAttached(); +} + +bool DbAttacherImpl::attachDatabases() +{ + dbNameToAttach.clear(); + + prepareNameToDbMap(); + + TokenList dbTokens = getDbTokens(); + QHash groupedDbTokens = groupDbTokens(dbTokens); + + if (!attachAllDbs(groupedDbTokens)) + return false; + + QHash tokenMapping = getTokenMapping(dbTokens); + replaceTokensInQueries(tokenMapping); + + return true; +} + +TokenList DbAttacherImpl::getDbTokens() +{ + TokenList dbTokens; + foreach (SqliteQueryPtr query, queries) + dbTokens += query->getContextDatabaseTokens(); + + return dbTokens; +} + +void DbAttacherImpl::detachAttached() +{ + foreach (const QString& dbName, dbNameToAttach.leftValues()) + db->detach(nameToDbMap[dbName]); + + dbNameToAttach.clear(); + nameToDbMap.clear(); +} + +void DbAttacherImpl::prepareNameToDbMap() +{ + foreach (Db* db, DBLIST->getValidDbList()) + nameToDbMap[db->getName()] = db; +} + +QHash DbAttacherImpl::groupDbTokens(const TokenList& dbTokens) +{ + // Filter out tokens of unknown databases and group results by name + QHash groupedDbTokens; + QString strippedName; + foreach (TokenPtr token, dbTokens) + { + strippedName = stripObjName(token->value, dialect); + if (!nameToDbMap.contains(strippedName, Qt::CaseInsensitive)) + continue; + + groupedDbTokens[strippedName] += token; + } + return groupedDbTokens; +} + +bool DbAttacherImpl::attachAllDbs(const QHash& groupedDbTokens) +{ + QString attachName; + foreach (const QString& dbName, groupedDbTokens.keys()) + { + attachName = db->attach(nameToDbMap[dbName]); + if (attachName.isNull()) + { + notifyError(QObject::tr("Could not attach database %1: %2").arg(dbName).arg(db->getErrorText())); + detachAttached(); + return false; + } + + dbNameToAttach.insert(dbName, attachName); + } + return true; +} + +QHash DbAttacherImpl::getTokenMapping(const TokenList& dbTokens) +{ + QHash tokenMapping; + QString strippedName; + TokenPtr dstToken; + foreach (TokenPtr srcToken, dbTokens) + { + strippedName = stripObjName(srcToken->value, dialect); + if (strippedName.compare("main", Qt::CaseInsensitive) == 0 || + strippedName.compare("temp", Qt::CaseInsensitive) == 0) + continue; + + if (!dbNameToAttach.containsLeft(strippedName, Qt::CaseInsensitive)) + { + qCritical() << "DB token to be replaced, but it's not on nameToAlias map! Token value:" << strippedName + << "and nameToAlias map has keys:" << dbNameToAttach.leftValues(); + continue; + } + dstToken = TokenPtr::create(dbNameToAttach.valueByLeft(strippedName, Qt::CaseInsensitive)); + tokenMapping[srcToken] = dstToken; + } + return tokenMapping; +} + +void DbAttacherImpl::replaceTokensInQueries(const QHash& tokenMapping) +{ + int idx; + QHashIterator it(tokenMapping); + while (it.hasNext()) + { + it.next(); + foreach (SqliteQueryPtr query, queries) + { + idx = query->tokens.indexOf(it.key()); + if (idx < 0) + continue; // token not in this query, most likely in other query + + query->tokens.replace(idx, it.value()); + } + } +} + +BiStrHash DbAttacherImpl::getDbNameToAttach() const +{ + return dbNameToAttach; +} + +QString DbAttacherImpl::getQuery() const +{ + QStringList queryStrings; + foreach (SqliteQueryPtr query, queries) + queryStrings << query->detokenize(); + + return queryStrings.join(";"); +} + +DbAttacher* DbAttacherDefaultFactory::create(Db* db) +{ + return new DbAttacherImpl(db); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.h b/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.h new file mode 100644 index 0000000..49307c6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.h @@ -0,0 +1,94 @@ +#ifndef DBATTACHERIMPL_H +#define DBATTACHERIMPL_H + +#include "dbattacher.h" + +class DbAttacherImpl : public DbAttacher +{ + public: + /** + * @brief Creates attacher with the given database as the main. + * @param db Database that the query will be executed on. + */ + DbAttacherImpl(Db* db); + + bool attachDatabases(const QString& query); + bool attachDatabases(const QList& queries); + bool attachDatabases(SqliteQueryPtr query); + void detachDatabases(); + BiStrHash getDbNameToAttach() const; + QString getQuery() const; + + private: + /** + * @brief Does the actual job, after having all input queries as parsed objects. + * @return true on success, false on failure. + */ + bool attachDatabases(); + + /** + * @brief Finds tokens representing databases in the query. + * @return List of tokens. Some tokens have non-printable value (spaces, etc), others are database names. + */ + TokenList getDbTokens(); + + /** + * @brief Detaches all databases currently attached by the attacher. + * + * Also clears names mappings. + */ + void detachAttached(); + + /** + * @brief Generates mapping of database name to its Db object for all registered databases. + */ + void prepareNameToDbMap(); + + /** + * @brief Groups tokens by the name of database they refer to. + * @param dbTokens Tokens representing databases in the query. + * + * This method is used to learn if some database is used more than once in the query, + * so we attach it only once, then replace all tokens referring to it by the attach name. + */ + QHash groupDbTokens(const TokenList& dbTokens); + + /** + * @brief Tries to attach all required databases. + * @param groupedDbTokens Database tokens grouped by database name, as returned from groupDbTokens(). + * @return true on success, false on any problem. + * + * Major problem that can happen is when "ATTACH 'path to file'" fails for any reason. In that case + * detachAttached() is called and false is returned. + */ + bool attachAllDbs(const QHash& groupedDbTokens); + + /** + * @brief Creates token-to-token replace map to update the query. + * @param dbTokens Tokens representing databases in the query. + * @return Mapping to be used when replacing tokens in the query. + */ + QHash getTokenMapping(const TokenList& dbTokens); + + /** + * @brief Replaces tokens in the query. + * @param tokenMapping Map of tokens to replace. + * + * Replacing takes place in token lists of each query in the queries member. + */ + void replaceTokensInQueries(const QHash& tokenMapping); + + QList queries; + Db* db = nullptr; + Dialect dialect; + BiStrHash dbNameToAttach; + StrHash nameToDbMap; +}; + +class DbAttacherDefaultFactory : public DbAttacherFactory +{ + public: + DbAttacher* create(Db* db); +}; + +#endif // DBATTACHERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/importworker.cpp b/SQLiteStudio3/coreSQLiteStudio/importworker.cpp new file mode 100644 index 0000000..af4a66d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/importworker.cpp @@ -0,0 +1,171 @@ +#include "importworker.h" +#include "schemaresolver.h" +#include "services/notifymanager.h" +#include "db/db.h" +#include "plugins/importplugin.h" +#include "common/utils.h" + +ImportWorker::ImportWorker(ImportPlugin* plugin, ImportManager::StandardImportConfig* config, Db* db, const QString& table, QObject *parent) : + QObject(parent), plugin(plugin), config(config), db(db), table(table) +{ +} + +void ImportWorker::run() +{ + if (!plugin->beforeImport(*config)) + { + emit finished(false); + return; + } + + readPluginColumns(); + if (columnsFromPlugin.size() == 0) + { + error(tr("No columns provided by the import plugin.")); + return; + } + + if (!db->begin()) + { + error(tr("Could not start transaction in order to import a data: %1").arg(db->getErrorText())); + return; + } + + if (!prepareTable()) + { + db->rollback(); + return; + } + + if (!importData()) + { + db->rollback(); + return; + } + + if (!db->commit()) + { + error(tr("Could not commit transaction for imported data: %1").arg(db->getErrorText())); + db->rollback(); + return; + } + + if (tableCreated) + emit createdTable(db, table); + + emit finished(true); +} + +void ImportWorker::interrupt() +{ + QMutexLocker locker(&interruptMutex); + interrupted = true; +} + +void ImportWorker::readPluginColumns() +{ + QList pluginColumnDefinitions = plugin->getColumns(); + for (const ImportPlugin::ColumnDefinition& colDef : pluginColumnDefinitions) + { + columnsFromPlugin << colDef.first; + columnTypesFromPlugin << colDef.second; + } +} + +void ImportWorker::error(const QString& err) +{ + notifyError(err); + plugin->afterImport(); + emit finished(false); +} + +bool ImportWorker::prepareTable() +{ + QStringList finalColumns; + Dialect dialect = db->getDialect(); + + SchemaResolver resolver(db); + tableColumns = resolver.getTableColumns(table); + if (tableColumns.size() > 0) + { + if (tableColumns.size() < columnsFromPlugin.size()) + { + notifyWarn(tr("Table '%1' has less columns than there are columns in the data to be imported. Excessive data columns will be ignored.").arg(table)); + finalColumns = tableColumns; + } + else if (tableColumns.size() > columnsFromPlugin.size()) + { + notifyInfo(tr("Table '%1' has more columns than there are columns in the data to be imported. Some columns in the table will be left empty.").arg(table)); + finalColumns = tableColumns.mid(0, columnsFromPlugin.size()); + } + else + { + finalColumns = tableColumns; + } + } + else + { + QStringList colDefs; + for (int i = 0; i < columnsFromPlugin.size(); i++) + colDefs << (columnsFromPlugin[i] + " " + columnTypesFromPlugin[i]).trimmed(); + + static const QString ddl = QStringLiteral("CREATE TABLE %1 (%2)"); + SqlQueryPtr result = db->exec(ddl.arg(wrapObjIfNeeded(table, dialect), colDefs.join(", "))); + if (result->isError()) + { + error(tr("Could not create table to import to: %1").arg(result->getErrorText())); + return false; + } + finalColumns = columnsFromPlugin; + tableCreated = true; + } + + if (isInterrupted()) + { + error(tr("Error while importing data: %1").arg(tr("Interrupted.", "import process status update"))); + return false; + } + + targetColumns = wrapObjNamesIfNeeded(finalColumns, dialect); + return true; +} + +bool ImportWorker::importData() +{ + static const QString insertTemplate = QStringLiteral("INSERT INTO %1 VALUES (%2)"); + + int colCount = targetColumns.size(); + QStringList valList; + for (int i = 0; i < colCount; i++) + valList << "?"; + + QString theInsert = insertTemplate.arg(wrapObjIfNeeded(table, db->getDialect()), valList.join(", ")); + SqlQueryPtr query = db->prepare(theInsert); + + int rowCnt = 0; + QList row; + while ((row = plugin->next()).size() > 0) + { + query->setArgs(row.mid(0, colCount)); + if (!query->execute()) + { + error(tr("Error while importing data: %1").arg(query->getErrorText())); + return false; + } + + if ((rowCnt % 100) == 0 && isInterrupted()) + { + error(tr("Error while importing data: %1").arg(tr("Interrupted.", "import process status update"))); + return false; + } + rowCnt++; + } + + return true; +} + +bool ImportWorker::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/importworker.h b/SQLiteStudio3/coreSQLiteStudio/importworker.h new file mode 100644 index 0000000..562ceea --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/importworker.h @@ -0,0 +1,44 @@ +#ifndef IMPORTWORKER_H +#define IMPORTWORKER_H + +#include "services/importmanager.h" +#include +#include +#include + +class ImportWorker : public QObject, public QRunnable +{ + Q_OBJECT + public: + ImportWorker(ImportPlugin* plugin, ImportManager::StandardImportConfig* config, Db* db, const QString& table, QObject *parent = 0); + + void run(); + + private: + void readPluginColumns(); + void error(const QString& err); + bool prepareTable(); + bool importData(); + bool isInterrupted(); + + ImportPlugin* plugin = nullptr; + ImportManager::StandardImportConfig* config = nullptr; + Db* db = nullptr; + QString table; + QStringList columnsFromPlugin; + QStringList columnTypesFromPlugin; + QStringList tableColumns; + QStringList targetColumns; + bool interrupted = false; + QMutex interruptMutex; + bool tableCreated = false; + + public slots: + void interrupt(); + + signals: + void createdTable(Db* db, const QString& table); + void finished(bool result); +}; + +#endif // IMPORTWORKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/interruptable.h b/SQLiteStudio3/coreSQLiteStudio/interruptable.h new file mode 100644 index 0000000..6a64855 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/interruptable.h @@ -0,0 +1,23 @@ +#ifndef INTERRUPTABLE_H +#define INTERRUPTABLE_H + +/** + * @brief The interruptable interface + * + * Represents anything that does some work, that can be interrupted. + */ +class Interruptable +{ + public: + + /** + * @brief Interrupts current execution. + * + * This method makes sense only when execution takes place in thread other, than the one calling this method. + * It interrupts execution - in most cases instantly. + * This method doesn't return until the interrupting is done. + */ + virtual void interrupt() = 0; +}; + +#endif // INTERRUPTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/diff_match.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/diff_match.txt new file mode 100644 index 0000000..030341a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/diff_match.txt @@ -0,0 +1,18 @@ +Copyright 2008 Google Inc. All Rights Reserved. +Author: fraser@google.com (Neil Fraser) +Author: mikeslemmer@gmail.com (Mike Slemmer) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Diff Match and Patch +http://code.google.com/p/google-diff-match-patch/ diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/fugue_icons.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/fugue_icons.txt new file mode 100644 index 0000000..4fc7b75 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/fugue_icons.txt @@ -0,0 +1,80 @@ +Fugue Icons + +(C) 2013 Yusuke Kamiyamane. All rights reserved. + +These icons are licensed under a Creative Commons +Attribution 3.0 License. + + +If you can't or don't want to provide attribution, please +purchase a royalty-free license. + + +I'm unavailable for custom icon design work. But your +suggestions are always welcome! + + +------------------------------------------------------------ + +All logos and trademarks in some icons are property of their +respective owners. + +------------------------------------------------------------ + +- geotag + + (C) Geotag Icon Project. All rights reserved. + + + Geotag icon is licensed under a Creative Commons + Attribution-Share Alike 3.0 License or LGPL. + + + +- language + + (C) Language Icon Project. All rights reserved. + + + Language icon is licensed under a Creative Commons + Attribution-Share Alike 3.0 License. + + +- open-share + + (C) Open Share Icon Project. All rights reserved. + + + Open Share icon is licensed under a Creative Commons + Attribution-Share Alike 3.0 License. + + +- opml + + (C) OPML Icon Project. All rights reserved. + + + OPML icon is licensed under a Creative Commons + Attribution-Share Alike 2.5 License. + + +- share + + (C) Share Icon Project. All rights reserved. + + + Share icon is licensed under a GPL or LGPL or BSD or + Creative Commons Attribution 2.5 License. + + + + + +- xfn + + (C) Wolfgang Bartelme. All rights reserved. + + + XFN icon is licensed under a Creative Commons + Attribution-Share Alike 2.5 License. + \ No newline at end of file diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt new file mode 100644 index 0000000..10926e8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt new file mode 100644 index 0000000..a0a60f5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt @@ -0,0 +1,502 @@ +GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +0the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/sqlitestudio_license.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/sqlitestudio_license.txt new file mode 100644 index 0000000..cc41c68 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/sqlitestudio_license.txt @@ -0,0 +1,15 @@ +SQLiteStudio, a cross-platform SQLite manager (http://sqlitestudio.pl). +Copyright (C) 2014 SalSoft (http://salsoft.com.pl) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/SQLiteStudio3/coreSQLiteStudio/log.cpp b/SQLiteStudio3/coreSQLiteStudio/log.cpp new file mode 100644 index 0000000..d54abdb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/log.cpp @@ -0,0 +1,47 @@ +#include "log.h" +#include +#include + +bool SQL_DEBUG = false; +QString SQL_DEBUG_FILTER = ""; + +void setSqlLoggingEnabled(bool enabled) +{ + SQL_DEBUG = enabled; +} + +void setSqlLoggingFilter(const QString& filter) +{ + SQL_DEBUG_FILTER = filter; +} + +void logSql(Db* db, const QString& str, const QHash& args, Db::Flags flags) +{ + if (!SQL_DEBUG) + return; + + if (!SQL_DEBUG_FILTER.isEmpty() && SQL_DEBUG_FILTER != db->getName()) + return; + + qDebug() << QString("SQL %1> %2").arg(db->getName()).arg(str) << "(flags:" << Db::flagsToString(flags) << ")"; + QHashIterator it(args); + while (it.hasNext()) + { + it.next(); + qDebug() << " SQL arg>" << it.key() << "=" << it.value(); + } +} + +void logSql(Db* db, const QString& str, const QList& args, Db::Flags flags) +{ + if (!SQL_DEBUG) + return; + + if (!SQL_DEBUG_FILTER.isEmpty() && SQL_DEBUG_FILTER != db->getName()) + return; + + qDebug() << QString("SQL %1> %2").arg(db->getName()).arg(str) << "(flags:" << Db::flagsToString(flags) << ")"; + int i = 0; + foreach (const QVariant& arg, args) + qDebug() << " SQL arg>" << i++ << "=" << arg; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/log.h b/SQLiteStudio3/coreSQLiteStudio/log.h new file mode 100644 index 0000000..f866c93 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/log.h @@ -0,0 +1,16 @@ +#ifndef LOG_H +#define LOG_H + +#include "db/db.h" +#include "guiSQLiteStudio_global.h" +#include +#include +#include +#include + +API_EXPORT void logSql(Db* db, const QString& str, const QHash& args, Db::Flags flags); +API_EXPORT void logSql(Db* db, const QString& str, const QList& args, Db::Flags flags); +API_EXPORT void setSqlLoggingEnabled(bool enabled); +API_EXPORT void setSqlLoggingFilter(const QString& filter); + +#endif // LOG_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.cpp new file mode 100644 index 0000000..6c840e7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.cpp @@ -0,0 +1,128 @@ +#include "sqlitealtertable.h" +#include "sqlitequerytype.h" +#include "common/global.h" + +SqliteAlterTable::SqliteAlterTable() +{ + queryType = SqliteQueryType::AlterTable; +} + +SqliteAlterTable::SqliteAlterTable(const SqliteAlterTable& other) + : SqliteQuery(other), command(other.command), newName(other.newName), database(other.database), table(other.table), columnKw(other.columnKw) +{ + DEEP_COPY_FIELD(SqliteCreateTable::Column, newColumn); +} + +SqliteAlterTable::SqliteAlterTable(const QString &name1, const QString &name2, const QString &newName) + : SqliteAlterTable() +{ + command = Command::RENAME; + initName(name1, name2); + this->newName = newName; +} + +SqliteAlterTable::SqliteAlterTable(const QString& name1, const QString& name2, bool columnKw, SqliteCreateTable::Column *column) + : SqliteAlterTable() +{ + command = Command::ADD_COLUMN; + initName(name1, name2); + this->columnKw = columnKw; + this->newColumn = column; + if (column) + column->setParent(this); +} + +SqliteAlterTable::~SqliteAlterTable() +{ +// if (newColumn) + // delete newColumn; +} + +SqliteStatement* SqliteAlterTable::clone() +{ + return new SqliteAlterTable(*this); +} + +QStringList SqliteAlterTable::getTablesInStatement() +{ + QStringList list; + if (!table.isNull()) + list << table; + + if (!newName.isNull()) + list << newName; + + return list; +} + +QStringList SqliteAlterTable::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteAlterTable::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteAlterTable::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList SqliteAlterTable::getFullObjectsInStatement() +{ + QList result; + + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + if (fullObj.isValid()) + result << fullObj; + + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqliteAlterTable::initName(const QString &name1, const QString &name2) +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +TokenList SqliteAlterTable::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("ALTER").withSpace().withKeyword("TABLE").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table).withSpace(); + + if (newColumn) + { + builder.withKeyword("ADD").withSpace(); + if (columnKw) + builder.withKeyword("COLUMN").withSpace(); + + builder.withStatement(newColumn); + } + else if (!newName.isNull()) + { + builder.withKeyword("RENAME").withSpace().withKeyword("TO").withSpace().withOther(newName, dialect); + } + + builder.withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.h new file mode 100644 index 0000000..360db45 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.h @@ -0,0 +1,46 @@ +#ifndef SQLITEALTERTABLE_H +#define SQLITEALTERTABLE_H + +#include "sqlitequery.h" +#include "sqlitecreatetable.h" + +class API_EXPORT SqliteAlterTable : public SqliteQuery +{ + public: + enum class Command + { + RENAME, + ADD_COLUMN, + null + }; + + SqliteAlterTable(); + SqliteAlterTable(const SqliteAlterTable& other); + SqliteAlterTable(const QString& name1, const QString& name2, const QString& newName); + SqliteAlterTable(const QString& name1, const QString& name2, bool columnKw, SqliteCreateTable::Column* column); + ~SqliteAlterTable(); + SqliteStatement* clone(); + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void initName(const QString& name1, const QString& name2); + + public: + Command command = Command::null; + QString newName = QString::null; + QString database = QString::null; + QString table = QString::null; + bool columnKw = false; + SqliteCreateTable::Column* newColumn = nullptr; +}; + +typedef QSharedPointer SqliteAlterTablePtr; + +#endif // SQLITEALTERTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.cpp new file mode 100644 index 0000000..d4e5778 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.cpp @@ -0,0 +1,80 @@ +#include "sqliteanalyze.h" +#include "sqlitequerytype.h" + +#include + +SqliteAnalyze::SqliteAnalyze() +{ + queryType = SqliteQueryType::Analyze; +} + +SqliteAnalyze::SqliteAnalyze(const SqliteAnalyze& other) : + SqliteQuery(other), database(other.database), table(other.table) +{ +} + +SqliteAnalyze::SqliteAnalyze(const QString &name1, const QString &name2) + : SqliteAnalyze() +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +SqliteStatement* SqliteAnalyze::clone() +{ + return new SqliteAnalyze(*this); +} + +QStringList SqliteAnalyze::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteAnalyze::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteAnalyze::getTableTokensInStatement() +{ + return getObjectTokenListFromNmDbnm(); +} + +TokenList SqliteAnalyze::getDatabaseTokensInStatement() +{ + return getDbTokenListFromNmDbnm(); +} + +QList SqliteAnalyze::getFullObjectsInStatement() +{ + QList result; + + FullObject fullObj = getFullObjectFromNmDbnm(FullObject::TABLE); + if (fullObj.isValid()) + result << fullObj; + + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteAnalyze::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("ANALYZE").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table).withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.h new file mode 100644 index 0000000..194e4c9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.h @@ -0,0 +1,29 @@ +#ifndef SQLITEANALYZE_H +#define SQLITEANALYZE_H + +#include "sqlitequery.h" +#include + +class API_EXPORT SqliteAnalyze : public SqliteQuery +{ + public: + SqliteAnalyze(); + SqliteAnalyze(const SqliteAnalyze& other); + SqliteAnalyze(const QString& name1, const QString& name2); + SqliteStatement* clone(); + + QString database = QString::null; + QString table = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteAnalyzePtr; + +#endif // SQLITEANALYZE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.cpp new file mode 100644 index 0000000..7d0b8a5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.cpp @@ -0,0 +1,63 @@ +#include "sqliteattach.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteAttach::SqliteAttach() +{ + queryType = SqliteQueryType::Attach; +} + +SqliteAttach::SqliteAttach(bool dbKw, SqliteExpr *url, SqliteExpr *name, SqliteExpr *key) + : SqliteAttach() +{ + databaseKw = dbKw; + databaseUrl = url; + this->name = name; + this->key = key; + + if (databaseUrl) + databaseUrl->setParent(this); + + if (name) + name->setParent(this); + + if (key) + key->setParent(this); +} + +SqliteAttach::SqliteAttach(const SqliteAttach& other) : + SqliteQuery(other), databaseKw(other.databaseKw) +{ + DEEP_COPY_FIELD(SqliteExpr, databaseUrl); + DEEP_COPY_FIELD(SqliteExpr, name); + DEEP_COPY_FIELD(SqliteExpr, key); +} + +SqliteAttach::~SqliteAttach() +{ +} + +SqliteStatement* SqliteAttach::clone() +{ + return new SqliteAttach(*this); +} + +TokenList SqliteAttach::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("ATTACH").withSpace(); + + if (databaseKw) + builder.withKeyword("DATABASE").withSpace(); + + builder.withStatement(databaseUrl).withSpace().withKeyword("AS").withSpace().withStatement(name); + if (key) + builder.withSpace().withKeyword("KEY").withSpace().withStatement(key); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.h new file mode 100644 index 0000000..55151a5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.h @@ -0,0 +1,28 @@ +#ifndef SQLITEATTACHDATABASE_H +#define SQLITEATTACHDATABASE_H + +#include "sqlitequery.h" + +class SqliteExpr; + +class API_EXPORT SqliteAttach : public SqliteQuery +{ + public: + SqliteAttach(); + SqliteAttach(const SqliteAttach& other); + SqliteAttach(bool dbKw, SqliteExpr* url, SqliteExpr* name, SqliteExpr* key); + ~SqliteAttach(); + SqliteStatement* clone(); + + bool databaseKw = false; + SqliteExpr* databaseUrl = nullptr; + SqliteExpr* name = nullptr; + SqliteExpr* key = nullptr; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteAttachPtr; + +#endif // SQLITEATTACHDATABASE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.cpp new file mode 100644 index 0000000..dcd9740 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.cpp @@ -0,0 +1,71 @@ +#include "sqlitebegintrans.h" +#include "sqlitequerytype.h" + +#include + +SqliteBeginTrans::SqliteBeginTrans() +{ + queryType = SqliteQueryType::BeginTrans; +} + +SqliteBeginTrans::SqliteBeginTrans(const SqliteBeginTrans& other) : + SqliteQuery(other), onConflict(other.onConflict), name(other.name), transactionKw(other.transactionKw), type(other.type) +{ +} + +SqliteBeginTrans::SqliteBeginTrans(SqliteBeginTrans::Type type, bool transactionKw, const QString& name) + : SqliteBeginTrans() +{ + this->type = type; + this->transactionKw = transactionKw; + this->name = name; +} + +SqliteBeginTrans::SqliteBeginTrans(bool transactionKw, const QString &name, SqliteConflictAlgo onConflict) +{ + this->onConflict = onConflict; + this->transactionKw = transactionKw; + this->name = name; +} + +SqliteStatement*SqliteBeginTrans::clone() +{ + return new SqliteBeginTrans(*this); +} + +QString SqliteBeginTrans::typeToString(SqliteBeginTrans::Type type) +{ + switch (type) + { + case Type::null: + return QString(); + case Type::DEFERRED: + return "DEFERRED"; + case Type::IMMEDIATE: + return "IMMEDIATE"; + case Type::EXCLUSIVE: + return "EXCLUSIVE"; + } + return QString(); +} + +TokenList SqliteBeginTrans::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("BEGIN"); + + if (type != Type::null) + builder.withSpace().withKeyword(typeToString(type)); + + if (transactionKw) + { + builder.withSpace().withKeyword("TRANSACTION"); + if (!name.isNull()) + builder.withSpace().withOther(name, dialect); + } + + builder.withConflict(onConflict).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.h new file mode 100644 index 0000000..48f5b37 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.h @@ -0,0 +1,38 @@ +#ifndef SQLITEBEGINTRANS_H +#define SQLITEBEGINTRANS_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" +#include + +class API_EXPORT SqliteBeginTrans : public SqliteQuery +{ + public: + enum class Type + { + null, + DEFERRED, + IMMEDIATE, + EXCLUSIVE + }; + + SqliteBeginTrans(); + SqliteBeginTrans(const SqliteBeginTrans& other); + SqliteBeginTrans(Type type, bool transactionKw, const QString& name); + SqliteBeginTrans(bool transactionKw, const QString& name, SqliteConflictAlgo onConflict); + SqliteStatement* clone(); + + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; // sqlite2 only + QString name; // in docs sqlite2 only, but in gramma it's also sqlite3 + bool transactionKw = false; + Type type = Type::null; // sqlite3 only + + static QString typeToString(Type type); + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteBeginTransPtr; + +#endif // SQLITEBEGINTRANS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.cpp new file mode 100644 index 0000000..cc773bb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.cpp @@ -0,0 +1,88 @@ +#include "sqlitecolumntype.h" +#include "parser/statementtokenbuilder.h" +#include "common/utils_sql.h" + +SqliteColumnType::SqliteColumnType() +{ +} + +SqliteColumnType::SqliteColumnType(const SqliteColumnType& other) : + SqliteStatement(other), name(other.name), scale(other.scale), precision(other.precision) +{ +} + +SqliteColumnType::SqliteColumnType(const QString &name) +{ + this->name = name; +} + +SqliteColumnType::SqliteColumnType(const QString &name, const QVariant& scale) +{ + this->name = name; + this->scale = scale; +} + +SqliteColumnType::SqliteColumnType(const QString &name, const QVariant& scale, const QVariant& precision) +{ + this->name = name; + this->precision = precision; + this->scale = scale; +} + +SqliteStatement* SqliteColumnType::clone() +{ + return new SqliteColumnType(*this); +} + +bool SqliteColumnType::isPrecisionDouble() +{ + return !precision.isNull() && precision.toString().indexOf(".") > -1; +} + +bool SqliteColumnType::isScaleDouble() +{ + return !scale.isNull() && scale.toString().indexOf(".") > -1; +} + +TokenList SqliteColumnType::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (name.isEmpty()) + return TokenList(); + + builder.withOther(name); + + if (!scale.isNull()) + { + builder.withSpace().withParLeft(); + if (scale.userType() == QVariant::Int) + builder.withInteger(scale.toInt()); + else if (scale.userType() == QVariant::LongLong) + builder.withInteger(scale.toLongLong()); + else if (scale.userType() == QVariant::Double) + builder.withFloat(scale.toDouble()); + else + builder.withOther(scale.toString()); + + if (!precision.isNull()) + { + builder.withOperator(",").withSpace(); + if (precision.userType() == QVariant::Int) + builder.withInteger(precision.toInt()); + else if (precision.userType() == QVariant::LongLong) + builder.withInteger(precision.toLongLong()); + else if (precision.userType() == QVariant::Double) + builder.withFloat(precision.toDouble()); + else + builder.withOther(precision.toString()); + } + builder.withParRight(); + } + + return builder.build(); +} + +DataType SqliteColumnType::toDataType() const +{ + return DataType(name, scale, precision); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.h new file mode 100644 index 0000000..fc87b6b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.h @@ -0,0 +1,30 @@ +#ifndef SQLITECOLUMNTYPE_H +#define SQLITECOLUMNTYPE_H + +#include "sqlitestatement.h" +#include "datatype.h" +#include + +class API_EXPORT SqliteColumnType : public SqliteStatement +{ + public: + SqliteColumnType(); + SqliteColumnType(const SqliteColumnType& other); + explicit SqliteColumnType(const QString& name); + SqliteColumnType(const QString& name, const QVariant &scale); + SqliteColumnType(const QString& name, const QVariant &scale, const QVariant &precision); + SqliteStatement* clone(); + + bool isPrecisionDouble(); + bool isScaleDouble(); + TokenList rebuildTokensFromContents(); + DataType toDataType() const; + + QString name = QString::null; + QVariant scale = QVariant(); // first size number + QVariant precision = QVariant(); // second size number +}; + +typedef QSharedPointer SqliteColumnTypePtr; + +#endif // SQLITECOLUMNTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.cpp new file mode 100644 index 0000000..97be8a9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.cpp @@ -0,0 +1,48 @@ +#include "sqlitecommittrans.h" +#include "sqlitequerytype.h" + +#include + +SqliteCommitTrans::SqliteCommitTrans() +{ + queryType = SqliteQueryType::CommitTrans; +} + +SqliteCommitTrans::SqliteCommitTrans(bool transactionKw, const QString& name, bool endKw) + : SqliteCommitTrans() +{ + this->endKw = endKw; + this->transactionKw = transactionKw; + this->name = name; +} + +SqliteCommitTrans::SqliteCommitTrans(const SqliteCommitTrans& other) : + SqliteQuery(other), endKw(other.endKw), name(other.name), transactionKw(other.transactionKw) +{ +} + +SqliteStatement* SqliteCommitTrans::clone() +{ + return new SqliteCommitTrans(*this); +} + +TokenList SqliteCommitTrans::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (endKw) + builder.withKeyword("END"); + else + builder.withKeyword("COMMIT"); + + if (transactionKw) + { + builder.withSpace().withKeyword("TRANSACTION"); + if (!name.isNull()) + builder.withSpace().withOther(name, dialect); + } + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.h new file mode 100644 index 0000000..ec418a6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.h @@ -0,0 +1,25 @@ +#ifndef SQLITECOMMITTRANS_H +#define SQLITECOMMITTRANS_H + +#include "sqlitequery.h" +#include + +class API_EXPORT SqliteCommitTrans : public SqliteQuery +{ + public: + SqliteCommitTrans(); + SqliteCommitTrans(bool transactionKw, const QString &name, bool endKw); + SqliteCommitTrans(const SqliteCommitTrans& other); + SqliteStatement* clone(); + + bool endKw = false; + QString name = QString::null; + bool transactionKw = false; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteCommitTransPtr; + +#endif // SQLITECOMMITTRANS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.cpp new file mode 100644 index 0000000..56fb42d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.cpp @@ -0,0 +1,37 @@ +#include "sqliteconflictalgo.h" + +SqliteConflictAlgo sqliteConflictAlgo(const QString& value) +{ + QString upper = value.toUpper(); + if (upper == "ROLLBACK") + return SqliteConflictAlgo::ROLLBACK; + else if (upper == "ABORT") + return SqliteConflictAlgo::ABORT; + else if (upper == "FAIL") + return SqliteConflictAlgo::FAIL; + else if (upper == "IGNORE") + return SqliteConflictAlgo::IGNORE; + else if (upper == "REPLACE") + return SqliteConflictAlgo::REPLACE; + else + return SqliteConflictAlgo::null; +} + +QString sqliteConflictAlgo(SqliteConflictAlgo value) +{ + switch (value) + { + case SqliteConflictAlgo::ROLLBACK: + return "ROLLBACK"; + case SqliteConflictAlgo::ABORT: + return "ABORT"; + case SqliteConflictAlgo::FAIL: + return "FAIL"; + case SqliteConflictAlgo::IGNORE: + return "IGNORE"; + case SqliteConflictAlgo::REPLACE: + return "REPLACE"; + default: + return QString::null; + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.h new file mode 100644 index 0000000..754672d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.h @@ -0,0 +1,20 @@ +#ifndef SQLITECONFLICTALGO_H +#define SQLITECONFLICTALGO_H + +#include "coreSQLiteStudio_global.h" +#include + +enum class SqliteConflictAlgo +{ + ROLLBACK, + ABORT, + FAIL, + IGNORE, + REPLACE, + null +}; + +API_EXPORT SqliteConflictAlgo sqliteConflictAlgo(const QString& value); +API_EXPORT QString sqliteConflictAlgo(SqliteConflictAlgo value); + +#endif // SQLITECONFLICTALGO_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.cpp new file mode 100644 index 0000000..009f836 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.cpp @@ -0,0 +1,92 @@ +#include "sqlitecopy.h" +#include "sqlitequerytype.h" + +#include + +SqliteCopy::SqliteCopy() +{ + queryType = SqliteQueryType::Copy; +} + +SqliteCopy::SqliteCopy(const SqliteCopy& other) : + SqliteQuery(other), onConflict(other.onConflict), database(other.database), table(other.table), file(other.file), delimiter(other.delimiter) +{ +} + +SqliteCopy::SqliteCopy(SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, const QString &name3, const QString &delim) + : SqliteCopy() +{ + this->onConflict = onConflict; + + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; + + file = name3; + delimiter = delim; +} + +SqliteStatement* SqliteCopy::clone() +{ + return new SqliteCopy(*this); +} + +QStringList SqliteCopy::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCopy::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCopy::getTableTokensInStatement() +{ + return getObjectTokenListFromNmDbnm(); +} + +TokenList SqliteCopy::getDatabaseTokensInStatement() +{ + return getDbTokenListFromNmDbnm(); +} + +QList SqliteCopy::getFullObjectsInStatement() +{ + QList result; + + FullObject fullObj = getFullObjectFromNmDbnm(FullObject::TABLE); + if (fullObj.isValid()) + result << fullObj; + + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +TokenList SqliteCopy::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("COPY").withSpace(); + if (onConflict != SqliteConflictAlgo::null) + builder.withKeyword("OR").withSpace().withKeyword(sqliteConflictAlgo(onConflict)).withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withSpace(); + + builder.withOther(table, dialect).withSpace().withKeyword("FROM").withSpace().withString(file); + + if (!delimiter.isNull()) + builder.withSpace().withKeyword("USING").withSpace().withKeyword("DELIMITERS").withSpace().withString(delimiter); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.h new file mode 100644 index 0000000..ff586df --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.h @@ -0,0 +1,32 @@ +#ifndef SQLITECOPY_H +#define SQLITECOPY_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" + +class API_EXPORT SqliteCopy : public SqliteQuery +{ + public: + SqliteCopy(); + SqliteCopy(const SqliteCopy& other); + SqliteCopy(SqliteConflictAlgo onConflict, const QString& name1, const QString& name2, const QString& name3, const QString& delim = QString::null); + SqliteStatement* clone(); + + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + QString database = QString::null; + QString table = QString::null; + QString file = QString::null; + QString delimiter = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteCopyPtr; + +#endif // SQLITECOPY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.cpp new file mode 100644 index 0000000..36f7aa9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.cpp @@ -0,0 +1,190 @@ +#include "sqlitecreateindex.h" +#include "sqlitequerytype.h" +#include "sqliteindexedcolumn.h" +#include "parser/statementtokenbuilder.h" +#include "parser/ast/sqliteexpr.h" +#include "common/global.h" + +SqliteCreateIndex::SqliteCreateIndex() +{ + queryType = SqliteQueryType::CreateIndex; +} + +SqliteCreateIndex::SqliteCreateIndex(const SqliteCreateIndex& other) : + SqliteQuery(other), uniqueKw(other.uniqueKw), ifNotExistsKw(other.ifNotExistsKw), database(other.database), index(other.index), + table(other.table) +{ + DEEP_COPY_COLLECTION(SqliteIndexedColumn, indexedColumns); +} + +SqliteCreateIndex::SqliteCreateIndex(bool unique, bool ifNotExists, const QString &name1, const QString &name2, const QString &name3, + const QList &columns, SqliteConflictAlgo onConflict) + : SqliteCreateIndex() +{ + // Constructor for SQLite 2 + uniqueKw = unique; + ifNotExistsKw = ifNotExists; + + index = name1; + + if (!name3.isNull()) + { + database = name2; + table = name3; + } + else + table = name2; + + this->onConflict = onConflict; + this->indexedColumns = columns; + + foreach (SqliteIndexedColumn* idxCol, columns) + idxCol->setParent(this); +} + +SqliteCreateIndex::SqliteCreateIndex(bool unique, bool ifNotExists, const QString& name1, const QString& name2, const QString& name3, + const QList& columns, SqliteExpr* where) + : SqliteCreateIndex() +{ + // Constructor for SQLite 3 + uniqueKw = unique; + ifNotExistsKw = ifNotExists; + + if (!name2.isNull()) + { + database = name1; + index = name2; + } + else + index = name1; + + table = name3; + this->indexedColumns = columns; + + foreach (SqliteIndexedColumn* idxCol, columns) + idxCol->setParent(this); + + this->where = where; +} + +SqliteCreateIndex::~SqliteCreateIndex() +{ +} + +SqliteStatement*SqliteCreateIndex::clone() +{ + return new SqliteCreateIndex(*this); +} + +QString SqliteCreateIndex::getTargetTable() const +{ + return table; +} + +QStringList SqliteCreateIndex::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCreateIndex::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateIndex::getTableTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return getObjectTokenListFromNmDbnm("nm2", "dbnm"); + else + return getTokenListFromNamedKey("nm2"); +} + +TokenList SqliteCreateIndex::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return getDbTokenListFromNmDbnm("nm2", "dbnm"); + else + return getDbTokenListFromNmDbnm(); +} + +QList SqliteCreateIndex::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj; + if (dialect == Dialect::Sqlite2) + fullObj = getFullObjectFromNmDbnm(FullObject::TABLE, "nm2", "dbnm"); + else + { + TokenList tableTokens = getTokenListFromNamedKey("nm2"); + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::TABLE, TokenPtr(), tableTokens[0]); + } + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + // Index object + if (dialect == Dialect::Sqlite2) + { + TokenList tableTokens = getTokenListFromNamedKey("nm"); + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::INDEX, TokenPtr(), tableTokens[0]); + } + else + fullObj = getFullObjectFromNmDbnm(FullObject::INDEX, "nm", "dbnm"); + + return result; +} + +TokenList SqliteCreateIndex::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("CREATE").withSpace(); + if (uniqueKw) + builder.withKeyword("UNIQUE").withSpace(); + + builder.withKeyword("INDEX").withSpace(); + + if (ifNotExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + + if (dialect == Dialect::Sqlite2) + { + builder.withOther(index, dialect).withSpace().withKeyword("ON").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect).withSpace(); + builder.withParLeft().withStatementList(indexedColumns).withParRight(); + + + if (onConflict != SqliteConflictAlgo::null) + builder.withSpace().withKeyword(sqliteConflictAlgo(onConflict)); + } + else + { + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(index, dialect).withSpace().withKeyword("ON").withSpace().withOther(table, dialect).withSpace().withParLeft() + .withStatementList(indexedColumns).withParRight(); + } + + if (where) + builder.withSpace().withKeyword("WHERE").withStatement(where); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.h new file mode 100644 index 0000000..9251b18 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.h @@ -0,0 +1,50 @@ +#ifndef SQLITECREATEINDEX_H +#define SQLITECREATEINDEX_H + +#include "sqlitequery.h" +#include "sqlitetablerelatedddl.h" +#include "sqliteconflictalgo.h" +#include "sqliteexpr.h" +#include +#include + +class SqliteIndexedColumn; + +class API_EXPORT SqliteCreateIndex : public SqliteQuery, public SqliteTableRelatedDdl +{ + public: + SqliteCreateIndex(); + SqliteCreateIndex(const SqliteCreateIndex& other); + SqliteCreateIndex(bool unique, bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3, const QList& columns, + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null); + SqliteCreateIndex(bool unique, bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3, const QList& columns, + SqliteExpr* where); + ~SqliteCreateIndex(); + SqliteStatement* clone(); + + QString getTargetTable() const; + + bool uniqueKw = false; + bool ifNotExistsKw = false; + QList indexedColumns; + // The database refers to index name in Sqlite3, but in Sqlite2 it refers to the table. + QString database = QString::null; + QString index = QString::null; + QString table = QString::null; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + SqliteExpr* where = nullptr; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteCreateIndexPtr; + +#endif // SQLITECREATEINDEX_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp new file mode 100644 index 0000000..e31e512 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp @@ -0,0 +1,771 @@ +#include "sqlitecreatetable.h" +#include "parser/statementtokenbuilder.h" +#include "common/utils_sql.h" +#include "common/global.h" + +SqliteCreateTable::SqliteCreateTable() +{ + queryType = SqliteQueryType::CreateTable; +} + +SqliteCreateTable::SqliteCreateTable(const SqliteCreateTable& other) : + SqliteQuery(other), ifNotExistsKw(other.ifNotExistsKw), tempKw(other.tempKw), temporaryKw(other.temporaryKw), + database(other.database), table(other.table), withOutRowId(other.withOutRowId) +{ + DEEP_COPY_COLLECTION(Column, columns); + DEEP_COPY_COLLECTION(Constraint, constraints); + DEEP_COPY_FIELD(SqliteSelect, select); +} + +SqliteCreateTable::SqliteCreateTable(bool ifNotExistsKw, int temp, const QString &name1, const QString &name2, const QList &columns, const QList& constraints) + : SqliteCreateTable() +{ + init(ifNotExistsKw, temp, name1, name2); + this->columns = columns; + foreach (Column* column, columns) + column->setParent(this); + + SqliteCreateTable::Constraint* constr = nullptr; + foreach (constr, constraints) + { + if (this->constraints.size() > 0 && + this->constraints.last()->type == SqliteCreateTable::Constraint::NAME_ONLY) + { + constr->name = this->constraints.last()->name; + delete this->constraints.takeLast(); + } + this->constraints << constr; + constr->setParent(this); + } +} + +SqliteCreateTable::SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2, const QList& columns, const QList& constraints, const QString& withOutRowId) : + SqliteCreateTable(ifNotExistsKw, temp, name1, name2, columns, constraints) +{ + this->withOutRowId = withOutRowId; +} + +SqliteCreateTable::SqliteCreateTable(bool ifNotExistsKw, int temp, const QString &name1, const QString &name2, SqliteSelect *select) + : SqliteCreateTable() +{ + init(ifNotExistsKw, temp, name1, name2); + this->select = select; + if (select) + select->setParent(this); +} + +SqliteCreateTable::~SqliteCreateTable() +{ +} + +SqliteStatement*SqliteCreateTable::clone() +{ + return new SqliteCreateTable(*this); +} + +QList SqliteCreateTable::getConstraints(SqliteCreateTable::Constraint::Type type) const +{ + QList results; + foreach (Constraint* constr, constraints) + if (constr->type == type) + results << constr; + + return results; +} + +SqliteStatement* SqliteCreateTable::getPrimaryKey() const +{ + foreach (Constraint* constr, getConstraints(Constraint::PRIMARY_KEY)) + return constr; + + Column::Constraint* colConstr = nullptr; + foreach (Column* col, columns) + { + colConstr = col->getConstraint(Column::Constraint::PRIMARY_KEY); + if (colConstr) + return colConstr; + } + + return nullptr; +} + +QStringList SqliteCreateTable::getPrimaryKeyColumns() const +{ + QStringList colNames; + SqliteStatement* primaryKey = getPrimaryKey(); + if (!primaryKey) + return colNames; + + SqliteCreateTable::Column::Constraint* columnConstr = dynamic_cast(primaryKey); + if (columnConstr) + { + colNames << dynamic_cast(columnConstr->parentStatement())->name; + return colNames; + } + + SqliteCreateTable::Constraint* tableConstr = dynamic_cast(primaryKey); + if (tableConstr) + { + foreach (SqliteIndexedColumn* idxCol, tableConstr->indexedColumns) + colNames << idxCol->name; + } + return colNames; +} + +SqliteCreateTable::Column* SqliteCreateTable::getColumn(const QString& colName) +{ + foreach (Column* col, columns) + { + if (col->name.compare(colName, Qt::CaseInsensitive) == 0) + return col; + } + return nullptr; +} + +QList SqliteCreateTable::getForeignKeysByTable(const QString& foreignTable) const +{ + QList results; + foreach (Constraint* constr, constraints) + if (constr->type == Constraint::FOREIGN_KEY && constr->foreignKey->foreignTable.compare(foreignTable, Qt::CaseInsensitive) == 0) + results << constr; + + return results; +} + +QList SqliteCreateTable::getColumnForeignKeysByTable(const QString& foreignTable) const +{ + QList results; + foreach (Column* col, columns) + results += col->getForeignKeysByTable(foreignTable); + + return results; +} + +QStringList SqliteCreateTable::getColumnNames() const +{ + QStringList names; + foreach (Column* col, columns) + names << col->name; + + return names; +} + +QHash SqliteCreateTable::getModifiedColumnsMap(bool lowercaseKeys, Qt::CaseSensitivity cs) const +{ + QHash colMap; + QString key; + foreach (Column* col, columns) + { + key = lowercaseKeys ? col->originalName.toLower() : col->originalName; + if (col->name.compare(col->originalName, cs) != 0) + colMap[key] = col->name; + } + + return colMap; +} + +QStringList SqliteCreateTable::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCreateTable::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateTable::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteCreateTable::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList SqliteCreateTable::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +TokenList SqliteCreateTable::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("CREATE"); + if (tempKw) + builder.withSpace().withKeyword("TEMP"); + else if (temporaryKw) + builder.withSpace().withKeyword("TEMPORARY"); + + builder.withSpace().withKeyword("TABLE"); + if (ifNotExistsKw) + builder.withSpace().withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS"); + + builder.withSpace(); + if (dialect == Dialect::Sqlite3 && !database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect); + + if (select) + builder.withSpace().withKeyword("AS").withSpace().withStatement(select); + else + { + builder.withSpace().withParLeft().withStatementList(columns); + if (constraints.size() > 0) + builder.withOperator(",").withStatementList(constraints); + + builder.withParRight(); + + if (!withOutRowId.isNull()) + builder.withSpace().withKeyword("WITHOUT").withSpace().withOther("ROWID"); + } + + builder.withOperator(";"); + + return builder.build(); +} + +void SqliteCreateTable::init(bool ifNotExistsKw, int temp, const QString &name1, const QString &name2) +{ + this->ifNotExistsKw = ifNotExistsKw; + if (temp == 2) + temporaryKw = true; + else if (temp == 1) + tempKw = true; + + if (name2.isNull()) + table = name1; + else + { + database = name1; + table = name2; + } +} + + +SqliteCreateTable::Column::Constraint::Constraint() +{ +} + +SqliteCreateTable::Column::Constraint::Constraint(const SqliteCreateTable::Column::Constraint& other) : + SqliteStatement(other), type(other.type), name(other.name), sortOrder(other.sortOrder), onConflict(other.onConflict), + autoincrKw(other.autoincrKw), literalValue(other.literalValue), literalNull(other.literalNull), ctime(other.ctime), id(other.id), + collationName(other.collationName), deferrable(other.deferrable), initially(other.initially) +{ + DEEP_COPY_FIELD(SqliteExpr, expr); + DEEP_COPY_FIELD(SqliteForeignKey, foreignKey); +} + +SqliteCreateTable::Column::Constraint::~Constraint() +{ +} + +SqliteStatement* SqliteCreateTable::Column::Constraint::clone() +{ + return new SqliteCreateTable::Column::Constraint(*this); +} + +void SqliteCreateTable::Column::Constraint::initDefNameOnly(const QString &name) +{ + this->type = SqliteCreateTable::Column::Constraint::NAME_ONLY; + this->name = name; +} + +void SqliteCreateTable::Column::Constraint::initDefId(const QString &id) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFAULT; + this->id = id; +} + +void SqliteCreateTable::Column::Constraint::initDefTerm(const QVariant &value, bool minus) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFAULT; + if (minus) + { + if (value.type() == QVariant::Double) + literalValue = -(value.toDouble()); + else if (value.type() == QVariant::LongLong) + literalValue = -(value.toLongLong()); + } + else if (value.isNull()) + { + literalValue = value; + literalNull = true; + } + else + literalValue = value; +} + +void SqliteCreateTable::Column::Constraint::initDefCTime(const QString &name) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFAULT; + ctime = name; +} + +void SqliteCreateTable::Column::Constraint::initDefExpr(SqliteExpr *expr) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFAULT; + this->expr = expr; + if (expr) + expr->setParent(this); +} + +void SqliteCreateTable::Column::Constraint::initNull(SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Column::Constraint::NULL_; + onConflict = algo; +} + +void SqliteCreateTable::Column::Constraint::initNotNull(SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Column::Constraint::NOT_NULL; + onConflict = algo; +} + +void SqliteCreateTable::Column::Constraint::initPk(SqliteSortOrder order, SqliteConflictAlgo algo, bool autoincr) +{ + this->type = SqliteCreateTable::Column::Constraint::PRIMARY_KEY; + sortOrder = order; + onConflict = algo; + autoincrKw = autoincr; +} + +void SqliteCreateTable::Column::Constraint::initUnique(SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Column::Constraint::UNIQUE; + onConflict = algo; +} + +void SqliteCreateTable::Column::Constraint::initCheck() +{ + this->type = SqliteCreateTable::Column::Constraint::CHECK; +} + +void SqliteCreateTable::Column::Constraint::initCheck(SqliteExpr *expr) +{ + this->type = SqliteCreateTable::Column::Constraint::CHECK; + this->expr = expr; + if (expr) + expr->setParent(this); +} + +void SqliteCreateTable::Column::Constraint::initCheck(SqliteExpr *expr, SqliteConflictAlgo algo) +{ + initCheck(expr); + this->onConflict = algo; +} + +void SqliteCreateTable::Column::Constraint::initFk(const QString& table, const QList& indexedColumns, const QList& conditions) +{ + this->type = SqliteCreateTable::Column::Constraint::FOREIGN_KEY; + + SqliteForeignKey* fk = new SqliteForeignKey(); + fk->foreignTable = table; + fk->indexedColumns = indexedColumns; + fk->conditions = conditions; + foreignKey = fk; + fk->setParent(this); + + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + idxCol->setParent(fk); + + foreach (SqliteForeignKey::Condition* cond, conditions) + cond->setParent(fk); +} + +void SqliteCreateTable::Column::Constraint::initDefer(SqliteInitially initially, SqliteDeferrable deferrable) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY; + this->deferrable = deferrable; + this->initially = initially; +} + +void SqliteCreateTable::Column::Constraint::initColl(const QString &name) +{ + this->type = SqliteCreateTable::Column::Constraint::COLLATE; + this->collationName = name; +} + +QString SqliteCreateTable::Column::Constraint::typeString() const +{ + switch (type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return "PRIMARY KEY"; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return "NOT NULL"; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return "UNIQUE"; + case SqliteCreateTable::Column::Constraint::CHECK: + return "CHECK"; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return "DEFAULT"; + case SqliteCreateTable::Column::Constraint::COLLATE: + return "COLLATE"; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return "FOREIGN KEY"; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return QString::null; +} + +SqliteCreateTable::Constraint::Constraint() +{ +} + +SqliteCreateTable::Constraint::Constraint(const SqliteCreateTable::Constraint& other) : + SqliteStatement(other), type(other.type), name(other.name), autoincrKw(other.autoincrKw), onConflict(other.onConflict), + afterComma(other.afterComma) +{ + DEEP_COPY_FIELD(SqliteForeignKey, foreignKey); + DEEP_COPY_FIELD(SqliteExpr, expr); + DEEP_COPY_COLLECTION(SqliteIndexedColumn, indexedColumns); +} + +SqliteCreateTable::Constraint::~Constraint() +{ +} + +SqliteStatement*SqliteCreateTable::Constraint::clone() +{ + return new SqliteCreateTable::Constraint(*this); +} + +void SqliteCreateTable::Constraint::initNameOnly(const QString &name) +{ + this->type = SqliteCreateTable::Constraint::NAME_ONLY; + this->name = name; +} + +void SqliteCreateTable::Constraint::initPk(const QList &indexedColumns, bool autoincr, SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Constraint::PRIMARY_KEY; + this->indexedColumns = indexedColumns; + autoincrKw = autoincr; + onConflict = algo; + + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + idxCol->setParent(this); +} + +void SqliteCreateTable::Constraint::initUnique(const QList &indexedColumns, SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Constraint::UNIQUE; + this->indexedColumns = indexedColumns; + onConflict = algo; + + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + idxCol->setParent(this); +} + +void SqliteCreateTable::Constraint::initCheck(SqliteExpr *expr, SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Constraint::CHECK; + this->expr = expr; + onConflict = algo; + if (expr) + expr->setParent(this); +} + +void SqliteCreateTable::Constraint::initCheck() +{ + this->type = SqliteCreateTable::Constraint::CHECK; +} + +void SqliteCreateTable::Constraint::initFk(const QList &indexedColumns, const QString& table, const QList &fkColumns, const QList &conditions, SqliteInitially initially, SqliteDeferrable deferrable) +{ + this->type = SqliteCreateTable::Constraint::FOREIGN_KEY; + this->indexedColumns = indexedColumns; + + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + idxCol->setParent(this); + + SqliteForeignKey* fk = new SqliteForeignKey(); + fk->foreignTable = table; + fk->indexedColumns = fkColumns; + fk->conditions = conditions; + fk->deferrable = deferrable; + fk->initially = initially; + + fk->setParent(this); + + foreach (SqliteIndexedColumn* idxCol, fkColumns) + idxCol->setParent(fk); + + foreach (SqliteForeignKey::Condition* cond, conditions) + cond->setParent(fk); + + this->foreignKey = fk; +} + +bool SqliteCreateTable::Constraint::doesAffectColumn(const QString& columnName) +{ + return getAffectedColumnIdx(columnName) > -1; +} + +int SqliteCreateTable::Constraint::getAffectedColumnIdx(const QString& columnName) +{ + int i = 0; + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + { + if (idxCol->name.compare(columnName, Qt::CaseInsensitive) == 0) + return i; + + i++; + } + + return -1; +} + +QString SqliteCreateTable::Constraint::typeString() const +{ + switch (type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return "PRIMARY KEY"; + case SqliteCreateTable::Constraint::UNIQUE: + return "UNIQUE"; + case SqliteCreateTable::Constraint::CHECK: + return "CHECK"; + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return "FOREIGN KEY"; + case SqliteCreateTable::Constraint::NAME_ONLY: + return QString::null; + } + return QString::null; +} + +TokenList SqliteCreateTable::Constraint::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (!name.isNull()) + builder.withKeyword("CONSTRAINT").withSpace().withOther(name, dialect).withSpace(); + + switch (type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + { + builder.withKeyword("PRIMARY").withSpace().withKeyword("KEY").withSpace().withParLeft().withStatementList(indexedColumns).withParRight(); + + if (autoincrKw) + builder.withSpace().withKeyword("AUTOINCREMENT"); + + builder.withConflict(onConflict); + break; + } + case SqliteCreateTable::Constraint::UNIQUE: + { + builder.withKeyword("UNIQUE").withSpace().withParLeft().withStatementList(indexedColumns).withParRight().withConflict(onConflict); + break; + } + case SqliteCreateTable::Constraint::CHECK: + { + builder.withKeyword("CHECK").withSpace().withParLeft().withStatement(expr).withParRight().withConflict(onConflict); + break; + } + case SqliteCreateTable::Constraint::FOREIGN_KEY: + { + builder.withKeyword("FOREIGN").withSpace().withKeyword("KEY").withSpace().withParLeft().withStatementList(indexedColumns) + .withParRight().withStatement(foreignKey); + break; + } + case SqliteCreateTable::Constraint::NAME_ONLY: + break; + } + + return builder.build(); +} + +SqliteCreateTable::Column::Column() +{ +} + +SqliteCreateTable::Column::Column(const SqliteCreateTable::Column& other) : + SqliteStatement(other), name(other.name), originalName(other.originalName) +{ + DEEP_COPY_FIELD(SqliteColumnType, type); + DEEP_COPY_COLLECTION(Constraint, constraints); +} + +SqliteCreateTable::Column::Column(const QString &name, SqliteColumnType *type, const QList &constraints) +{ + this->name = name; + this->originalName = name; + this->type = type; + + if (type) + type->setParent(this); + + SqliteCreateTable::Column::Constraint* constr = nullptr; + foreach (constr, constraints) + { + // If last constraint on list is NAME_ONLY we apply the name + // to current constraint and remove NAME_ONLY. + // Exception is DEFERRABLE_ONLY. + if (this->constraints.size() > 0 && + this->constraints.last()->type == SqliteCreateTable::Column::Constraint::NAME_ONLY && + constr->type != SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY) + { + constr->name = this->constraints.last()->name; + delete this->constraints.takeLast(); + } + + // And the opposite of above. Now we apply DEFERRABLE_ONLY, + // but only if last item in the list is not NAME_ONLY. + if (constr->type == SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY && + this->constraints.size() > 0 && + this->constraints.last()->type != SqliteCreateTable::Column::Constraint::NAME_ONLY) + { + SqliteCreateTable::Column::Constraint* last = this->constraints.last(); + last->deferrable = constr->deferrable; + last->initially = constr->initially; + delete constr; + + // We don't want deleted constr to be added to list. We finish this now. + continue; + } + + this->constraints << constr; + constr->setParent(this); + } +} + +SqliteCreateTable::Column::~Column() +{ +} + +SqliteStatement*SqliteCreateTable::Column::clone() +{ + return new SqliteCreateTable::Column(*this); +} + +bool SqliteCreateTable::Column::hasConstraint(SqliteCreateTable::Column::Constraint::Type type) const +{ + return getConstraint(type) != nullptr; +} + +SqliteCreateTable::Column::Constraint* SqliteCreateTable::Column::getConstraint(SqliteCreateTable::Column::Constraint::Type type) const +{ + foreach (Constraint* constr, constraints) + if (constr->type == type) + return constr; + + return nullptr; +} + +QList SqliteCreateTable::Column::getForeignKeysByTable(const QString& foreignTable) const +{ + QList results; + foreach (Constraint* constr, constraints) + if (constr->type == Constraint::FOREIGN_KEY && constr->foreignKey->foreignTable.compare(foreignTable, Qt::CaseInsensitive) == 0) + results << constr; + + return results; +} + +QStringList SqliteCreateTable::Column::getColumnsInStatement() +{ + return getStrListFromValue(name); +} + +TokenList SqliteCreateTable::Column::getColumnTokensInStatement() +{ + return getTokenListFromNamedKey("columnid"); +} + +TokenList SqliteCreateTable::Column::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withOther(name, dialect).withStatement(type).withStatementList(constraints, ""); + return builder.build(); +} + +TokenList SqliteCreateTable::Column::Constraint::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (!name.isNull()) + builder.withKeyword("CONSTRAINT").withSpace().withOther(name, dialect).withSpace(); + + switch (type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + { + builder.withKeyword("PRIMARY").withSpace().withKeyword("KEY").withSortOrder(sortOrder).withConflict(onConflict); + if (autoincrKw) + builder.withSpace().withKeyword("AUTOINCREMENT"); + + break; + } + case SqliteCreateTable::Column::Constraint::NOT_NULL: + { + builder.withKeyword("NOT").withSpace().withKeyword("NULL").withConflict(onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::UNIQUE: + { + builder.withKeyword("UNIQUE").withConflict(onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::CHECK: + { + builder.withKeyword("CHECK").withSpace().withParLeft().withStatement(expr).withParRight().withConflict(onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::DEFAULT: + { + builder.withKeyword("DEFAULT").withSpace(); + if (!id.isNull()) + builder.withOther(id); + else if (!ctime.isNull()) + builder.withKeyword(ctime.toUpper()); + else if (expr) + builder.withParLeft().withStatement(expr).withParRight(); + else if (literalNull) + builder.withKeyword("NULL"); + else + builder.withLiteralValue(literalValue); + + break; + } + case SqliteCreateTable::Column::Constraint::COLLATE: + { + builder.withKeyword("COLLATE").withSpace().withOther(collationName, dialect); + break; + } + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + { + builder.withStatement(foreignKey); + break; + } + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h new file mode 100644 index 0000000..f3be244 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h @@ -0,0 +1,205 @@ +#ifndef SQLITECREATETABLE_H +#define SQLITECREATETABLE_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" +#include "sqliteexpr.h" +#include "sqliteforeignkey.h" +#include "sqliteindexedcolumn.h" +#include "sqliteselect.h" +#include "sqlitecolumntype.h" +#include "sqlitesortorder.h" +#include "sqlitedeferrable.h" +#include +#include + +class API_EXPORT SqliteCreateTable : public SqliteQuery +{ + public: + class API_EXPORT Column : public SqliteStatement + { + public: + class API_EXPORT Constraint : public SqliteStatement + { + public: + enum Type + { + PRIMARY_KEY, + NOT_NULL, + UNIQUE, + CHECK, + DEFAULT, + COLLATE, + FOREIGN_KEY, + NULL_, // not officially supported + NAME_ONLY, // unofficial, because of bizarre sqlite grammar + DEFERRABLE_ONLY // unofficial, because of bizarre sqlite grammar + }; + + Constraint(); + Constraint(const Constraint& other); + ~Constraint(); + SqliteStatement* clone(); + + void initDefNameOnly(const QString& name); + void initDefId(const QString& id); + void initDefTerm(const QVariant& value, bool minus = false); + void initDefCTime(const QString& name); + void initDefExpr(SqliteExpr* expr); + void initNull(SqliteConflictAlgo algo); + void initNotNull(SqliteConflictAlgo algo); + void initPk(SqliteSortOrder order, SqliteConflictAlgo algo, bool autoincr); + void initUnique(SqliteConflictAlgo algo); + void initCheck(); + void initCheck(SqliteExpr* expr); + void initCheck(SqliteExpr* expr, SqliteConflictAlgo algo); + void initFk(const QString& table, const QList& indexedColumns, const QList& conditions); + void initDefer(SqliteInitially initially, SqliteDeferrable deferrable); + void initColl(const QString& name); + QString typeString() const; + + Type type; + QString name = QString::null; + SqliteSortOrder sortOrder = SqliteSortOrder::null; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + bool autoincrKw = false; + SqliteExpr* expr = nullptr; + QVariant literalValue; + bool literalNull = false; + QString ctime; + QString id; + QString collationName = QString::null; + SqliteForeignKey* foreignKey = nullptr; + SqliteDeferrable deferrable = SqliteDeferrable::null; + SqliteInitially initially = SqliteInitially::null; + + protected: + TokenList rebuildTokensFromContents(); + }; + + typedef QSharedPointer ConstraintPtr; + + Column(); + Column(const Column& other); + Column(const QString& name, SqliteColumnType* type, + const QList& constraints); + ~Column(); + SqliteStatement* clone(); + + bool hasConstraint(Constraint::Type type) const; + Constraint* getConstraint(Constraint::Type type) const; + QList getForeignKeysByTable(const QString& foreignTable) const; + + QString name = QString::null; + SqliteColumnType* type = nullptr; + QList constraints; + + /** + * @brief originalName + * Used to remember original name when column was edited and the name was changed. + * It's defined in the constructor to the same value as the name member. + */ + QString originalName = QString::null; + + protected: + QStringList getColumnsInStatement(); + TokenList getColumnTokensInStatement(); + TokenList rebuildTokensFromContents(); + }; + + typedef QSharedPointer ColumnPtr; + + class API_EXPORT Constraint : public SqliteStatement + { + public: + enum Type + { + PRIMARY_KEY, + UNIQUE, + CHECK, + FOREIGN_KEY, + NAME_ONLY // unofficial, because of bizarre sqlite grammar + }; + + Constraint(); + Constraint(const Constraint& other); + ~Constraint(); + SqliteStatement* clone(); + + void initNameOnly(const QString& name); + void initPk(const QList& indexedColumns, + bool autoincr, SqliteConflictAlgo algo); + void initUnique(const QList& indexedColumns, + SqliteConflictAlgo algo); + void initCheck(SqliteExpr* expr, SqliteConflictAlgo algo); + void initCheck(); + void initFk(const QList& indexedColumns, const QString& table, + const QList& fkColumns, const QList& conditions, + SqliteInitially initially, SqliteDeferrable deferrable); + + bool doesAffectColumn(const QString& columnName); + int getAffectedColumnIdx(const QString& columnName); + QString typeString() const; + + Type type; + QString name = QString::null; + bool autoincrKw = false; // not in docs, but needs to be supported + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + SqliteForeignKey* foreignKey = nullptr; + SqliteExpr* expr = nullptr; + QList indexedColumns; + bool afterComma = false; + + protected: + TokenList rebuildTokensFromContents(); + }; + + typedef QSharedPointer ConstraintPtr; + + SqliteCreateTable(); + SqliteCreateTable(const SqliteCreateTable& other); + SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2, + const QList& columns, const QList& constraints); + SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2, + const QList& columns, const QList& constraints, + const QString& withOutRowId); + SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2, + SqliteSelect* select); + ~SqliteCreateTable(); + SqliteStatement* clone(); + + QList getConstraints(Constraint::Type type) const; + SqliteStatement* getPrimaryKey() const; + QStringList getPrimaryKeyColumns() const; + Column* getColumn(const QString& colName); + QList getForeignKeysByTable(const QString& foreignTable) const; + QList getColumnForeignKeysByTable(const QString& foreignTable) const; + QStringList getColumnNames() const; + QHash getModifiedColumnsMap(bool lowercaseKeys = false, Qt::CaseSensitivity cs = Qt::CaseInsensitive) const; + + bool ifNotExistsKw = false; + bool tempKw = false; + bool temporaryKw = false; + QString database = QString::null; + QString table = QString::null; + QList columns; + QList constraints; + SqliteSelect* select = nullptr; + QString withOutRowId = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void init(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2); + +}; + +typedef QSharedPointer SqliteCreateTablePtr; + +#endif // SQLITECREATETABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.cpp new file mode 100644 index 0000000..3b7b0ea --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.cpp @@ -0,0 +1,381 @@ +#include "sqlitecreatetrigger.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "sqlitedelete.h" +#include "sqliteinsert.h" +#include "sqliteupdate.h" +#include "sqliteselect.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteCreateTrigger::SqliteCreateTrigger() +{ + queryType = SqliteQueryType::CreateTrigger; +} + +SqliteCreateTrigger::SqliteCreateTrigger(const SqliteCreateTrigger& other) : + SqliteQuery(other), tempKw(other.tempKw), temporaryKw(other.temporaryKw), ifNotExistsKw(other.ifNotExistsKw), database(other.database), + trigger(other.trigger), table(other.table), eventTime(other.eventTime), scope(other.scope) +{ + DEEP_COPY_FIELD(Event, event); + DEEP_COPY_FIELD(SqliteExpr, precondition); + + // Special case of deep collection copy + SqliteQuery* newQuery = nullptr; + foreach (SqliteQuery* query, other.queries) + { + switch (query->queryType) + { + case SqliteQueryType::Delete: + newQuery = new SqliteDelete(*dynamic_cast(query)); + break; + case SqliteQueryType::Insert: + newQuery = new SqliteInsert(*dynamic_cast(query)); + break; + case SqliteQueryType::Update: + newQuery = new SqliteUpdate(*dynamic_cast(query)); + break; + case SqliteQueryType::Select: + newQuery = new SqliteSelect(*dynamic_cast(query)); + break; + default: + newQuery = nullptr; + break; + } + + if (!newQuery) + continue; + + newQuery->setParent(this); + queries << newQuery; + } +} + +SqliteCreateTrigger::SqliteCreateTrigger(int temp, bool ifNotExists, const QString &name1, const QString &name2, const QString &name3, Time time, SqliteCreateTrigger::Event *event, Scope foreachType, SqliteExpr *when, const QList &queries, int sqliteVersion) : + SqliteCreateTrigger() +{ + this->ifNotExistsKw = ifNotExists; + this->scope = foreachType; + if (temp == 2) + temporaryKw = true; + else if (temp == 1) + tempKw = true; + + if (sqliteVersion == 3) + { + if (name2.isNull()) + trigger = name1; + else + { + database = name1; + trigger = name2; + } + table = name3; + } + else + { + trigger = name1; + if (name3.isNull()) + table = name2; + else + { + database = name2; + table = name3; + } + } + + this->event = event; + eventTime = time; + this->precondition = when; + this->queries = queries; + + if (event) + event->setParent(this); + + if (when) + when->setParent(this); + + foreach (SqliteQuery* q, queries) + q->setParent(this); +} + +SqliteCreateTrigger::~SqliteCreateTrigger() +{ +} + +SqliteStatement*SqliteCreateTrigger::clone() +{ + return new SqliteCreateTrigger(*this); +} + +QString SqliteCreateTrigger::getTargetTable() const +{ + return table; +} + +QString SqliteCreateTrigger::time(SqliteCreateTrigger::Time eventTime) +{ + switch (eventTime) + { + case SqliteCreateTrigger::Time::BEFORE: + return "BEFORE"; + case SqliteCreateTrigger::Time::AFTER: + return "AFTER"; + case SqliteCreateTrigger::Time::INSTEAD_OF: + return "INSTEAD OF"; + case SqliteCreateTrigger::Time::null: + break; + } + return QString::null; +} + +SqliteCreateTrigger::Time SqliteCreateTrigger::time(const QString& eventTime) +{ + if (eventTime == "BEFORE") + return Time::BEFORE; + + if (eventTime == "AFTER") + return Time::AFTER; + + if (eventTime == "INSTEAD OF") + return Time::INSTEAD_OF; + + return Time::null; +} + +QString SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope scope) +{ + switch (scope) + { + case SqliteCreateTrigger::Scope::FOR_EACH_ROW: + return "FOR EACH ROW"; + case SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT: + return "FOR EACH STATEMENT"; + case SqliteCreateTrigger::Scope::null: + break; + } + return QString::null; +} + +SqliteCreateTrigger::Scope SqliteCreateTrigger::stringToScope(const QString& scope) +{ + if (scope == "FOR EACH ROW") + return Scope::FOR_EACH_ROW; + + if (scope == "FOR EACH STATEMENT") + return Scope::FOR_EACH_STATEMENT; + + return Scope::null; +} + +QStringList SqliteCreateTrigger::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCreateTrigger::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateTrigger::getTableTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return getObjectTokenListFromNmDbnm("nm2", "dbnm"); + else + return getTokenListFromNamedKey("nm2"); +} + +TokenList SqliteCreateTrigger::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return getDbTokenListFromNmDbnm("nm2", "dbnm"); + else + return getDbTokenListFromNmDbnm(); +} + +QList SqliteCreateTrigger::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj; + if (dialect == Dialect::Sqlite2) + fullObj = getFullObjectFromNmDbnm(FullObject::TABLE, "nm2", "dbnm"); + else + { + TokenList tableTokens = getTokenListFromNamedKey("nm2"); + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::TABLE, TokenPtr(), tableTokens[0]); + } + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + // Trigger object + if (dialect == Dialect::Sqlite2) + { + TokenList tableTokens = getTokenListFromNamedKey("nm"); + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::TRIGGER, TokenPtr(), tableTokens[0]); + } + else + fullObj = getFullObjectFromNmDbnm(FullObject::TRIGGER, "nm", "dbnm"); + + return result; +} + +SqliteCreateTrigger::Event::Event() +{ + this->type = Event::null; +} + +SqliteCreateTrigger::Event::Event(SqliteCreateTrigger::Event::Type type) +{ + this->type = type; +} + +SqliteCreateTrigger::Event::Event(const SqliteCreateTrigger::Event& other) : + SqliteStatement(other), type(other.type), columnNames(other.columnNames) +{ +} + +SqliteCreateTrigger::Event::Event(const QList &columns) +{ + this->type = UPDATE_OF; + columnNames = columns; +} + +SqliteStatement*SqliteCreateTrigger::Event::clone() +{ + return new SqliteCreateTrigger::Event(*this); +} + +TokenList SqliteCreateTrigger::Event::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + switch (type) + { + case SqliteCreateTrigger::Event::INSERT: + builder.withKeyword("INSERT"); + break; + case SqliteCreateTrigger::Event::UPDATE: + builder.withKeyword("UPDATE"); + break; + case SqliteCreateTrigger::Event::DELETE: + builder.withKeyword("DELETE"); + break; + case SqliteCreateTrigger::Event::UPDATE_OF: + builder.withKeyword("UPDATE").withSpace().withKeyword("OF").withSpace().withOtherList(columnNames); + break; + case SqliteCreateTrigger::Event::null: + break; + } + + return builder.build(); +} + +QString SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::Type type) +{ + switch (type) + { + case SqliteCreateTrigger::Event::INSERT: + return "INSERT"; + case SqliteCreateTrigger::Event::UPDATE: + return "UPDATE"; + case SqliteCreateTrigger::Event::DELETE: + return "DELETE"; + case SqliteCreateTrigger::Event::UPDATE_OF: + return "UPDATE OF"; + case SqliteCreateTrigger::Event::null: + break; + } + return QString::null; +} + +SqliteCreateTrigger::Event::Type SqliteCreateTrigger::Event::stringToType(const QString& type) +{ + if (type == "INSERT") + return INSERT; + + if (type == "UPDATE") + return UPDATE; + + if (type == "DELETE") + return DELETE; + + if (type == "UPDATE OF") + return UPDATE_OF; + + return Event::null; +} + +TokenList SqliteCreateTrigger::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("CREATE").withSpace(); + if (tempKw) + builder.withKeyword("TEMP").withSpace(); + else if (temporaryKw) + builder.withKeyword("TEMPORARY").withSpace(); + + builder.withKeyword("TRIGGER").withSpace(); + if (ifNotExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + + if (dialect == Dialect::Sqlite3 && !database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(trigger, dialect).withSpace(); + switch (eventTime) + { + case Time::BEFORE: + builder.withKeyword("BEFORE").withSpace(); + break; + case Time::AFTER: + builder.withKeyword("AFTER").withSpace(); + break; + case Time::INSTEAD_OF: + builder.withKeyword("INSTEAD").withSpace().withKeyword("OF").withSpace(); + break; + case Time::null: + break; + } + + builder.withStatement(event).withSpace().withKeyword("ON").withSpace(); + if (dialect == Dialect::Sqlite2 && !database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect).withSpace(); + + switch (scope) + { + case SqliteCreateTrigger::Scope::FOR_EACH_ROW: + builder.withKeyword("FOR").withSpace().withKeyword("EACH").withSpace().withKeyword("ROW").withSpace(); + break; + case SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT: + builder.withKeyword("FOR").withSpace().withKeyword("EACH").withSpace().withKeyword("STATEMENT").withSpace(); + break; + case SqliteCreateTrigger::Scope::null: + break; + } + + if (precondition) + builder.withKeyword("WHEN").withStatement(precondition).withSpace(); + + builder.withKeyword("BEGIN").withSpace().withStatementList(queries, ";").withOperator(";").withSpace().withKeyword("END"); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.h new file mode 100644 index 0000000..2fb8ae4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.h @@ -0,0 +1,96 @@ +#ifndef SQLITECREATETRIGGER_H +#define SQLITECREATETRIGGER_H + +#include "sqlitequery.h" +#include "sqlitetablerelatedddl.h" + +#include +#include + +class SqliteExpr; + +class API_EXPORT SqliteCreateTrigger : public SqliteQuery, public SqliteTableRelatedDdl +{ + public: + enum class Time + { + BEFORE, + AFTER, + INSTEAD_OF, + null + }; + + class API_EXPORT Event : public SqliteStatement + { + public: + enum Type + { + INSERT, + UPDATE, + DELETE, + UPDATE_OF, + null + }; + + Event(); + explicit Event(Type type); + Event(const Event& other); + explicit Event(const QList& columns); + SqliteStatement* clone(); + + TokenList rebuildTokensFromContents(); + + static QString typeToString(Type type); + static Type stringToType(const QString& type); + + Type type; + QStringList columnNames; + }; + + enum class Scope + { + FOR_EACH_ROW, + FOR_EACH_STATEMENT, // Sqlite2 only + null + }; + + SqliteCreateTrigger(); + SqliteCreateTrigger(const SqliteCreateTrigger& other); + SqliteCreateTrigger(int temp, bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3, Time time, Event* event, Scope scope, + SqliteExpr* precondition, const QList& queries, int sqliteVersion); + ~SqliteCreateTrigger(); + SqliteStatement* clone(); + + QString getTargetTable() const; + + bool tempKw = false; + bool temporaryKw = false; + bool ifNotExistsKw = false; + // The database refers to the trigger name in Sqlite3, but in Sqlite2 it refers to the table. + QString database = QString::null; + QString trigger = QString::null; + QString table = QString::null; // can also be a view name + Event* event = nullptr; + Time eventTime = Time::null; + Scope scope = Scope::null; + SqliteExpr* precondition = nullptr; + QList queries; + + static QString time(Time eventTime); + static Time time(const QString& eventTime); + static QString scopeToString(Scope scope); + static Scope stringToScope(const QString& scope); + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteCreateTriggerPtr; + +#endif // SQLITECREATETRIGGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.cpp new file mode 100644 index 0000000..6b11c3c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.cpp @@ -0,0 +1,120 @@ +#include "sqlitecreateview.h" +#include "sqliteselect.h" +#include "sqlitequerytype.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteCreateView::SqliteCreateView() +{ + queryType = SqliteQueryType::CreateView; +} + +SqliteCreateView::SqliteCreateView(const SqliteCreateView& other) : + SqliteQuery(other), tempKw(other.tempKw), temporaryKw(other.temporaryKw), ifNotExists(other.ifNotExists), + database(other.database), view(other.view) +{ + DEEP_COPY_FIELD(SqliteSelect, select); + +} + +SqliteCreateView::SqliteCreateView(int temp, bool ifNotExists, const QString &name1, const QString &name2, SqliteSelect *select) : + SqliteCreateView() +{ + this->ifNotExists = ifNotExists; + + if (name2.isNull()) + view = name1; + else + { + database = name1; + view = name2; + } + + if (temp == 2) + temporaryKw = true; + else if (temp == 1) + tempKw = true; + + this->select = select; + + if (select) + select->setParent(this); +} + +SqliteCreateView::~SqliteCreateView() +{ +} + +SqliteStatement*SqliteCreateView::clone() +{ + return new SqliteCreateView(*this); +} + +QStringList SqliteCreateView::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateView::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite3) + return getDbTokenListFromFullname(); + else + return TokenList(); +} + +QList SqliteCreateView::getFullObjectsInStatement() +{ + QList result; + + // View object + FullObject fullObj; + if (dialect == Dialect::Sqlite3) + fullObj = getFullObjectFromFullname(FullObject::VIEW); + else + { + TokenList tokens = getTokenListFromNamedKey("nm"); + if (tokens.size() > 0) + fullObj = getFullObject(FullObject::VIEW, TokenPtr(), tokens[0]); + } + + if (fullObj.isValid()) + result << fullObj; + + // Db object + if (dialect == Dialect::Sqlite3) + { + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + } + + return result; +} + +TokenList SqliteCreateView::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("CREATE").withSpace(); + if (tempKw) + builder.withKeyword("TEMP").withSpace(); + else if (temporaryKw) + builder.withKeyword("TEMPORARY").withSpace(); + + builder.withKeyword("VIEW").withSpace(); + if (ifNotExists) + builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + + if (dialect == Dialect::Sqlite3 && !database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(view, dialect).withSpace().withKeyword("AS").withStatement(select); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.h new file mode 100644 index 0000000..4858227 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.h @@ -0,0 +1,35 @@ +#ifndef SQLITECREATEVIEW_H +#define SQLITECREATEVIEW_H + +#include "sqlitequery.h" +#include + +class SqliteSelect; + +class API_EXPORT SqliteCreateView : public SqliteQuery +{ + public: + SqliteCreateView(); + SqliteCreateView(const SqliteCreateView& other); + SqliteCreateView(int temp, bool ifNotExists, const QString& name1, const QString& name2, SqliteSelect* select); + ~SqliteCreateView(); + + SqliteStatement* clone(); + + bool tempKw = false; + bool temporaryKw = false; + bool ifNotExists = false; + QString database = QString::null; + QString view = QString::null; + SqliteSelect* select = nullptr; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteCreateViewPtr; + +#endif // SQLITECREATEVIEW_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.cpp new file mode 100644 index 0000000..bc38dc9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.cpp @@ -0,0 +1,120 @@ +#include "sqlitecreatevirtualtable.h" +#include "sqlitequerytype.h" + +#include +#include + +SqliteCreateVirtualTable::SqliteCreateVirtualTable() +{ + queryType = SqliteQueryType::CreateVirtualTable; +} + +SqliteCreateVirtualTable::SqliteCreateVirtualTable(const SqliteCreateVirtualTable& other) : + SqliteQuery(other), ifNotExistsKw(other.ifNotExistsKw), database(other.database), table(other.table), module(other.module), args(other.args) +{ +} + +SqliteCreateVirtualTable::SqliteCreateVirtualTable(bool ifNotExists, const QString &name1, const QString &name2, const QString &name3) : + SqliteCreateVirtualTable() +{ + initName(name1, name2); + this->ifNotExistsKw = ifNotExists; + module = name3; +} + +SqliteCreateVirtualTable::SqliteCreateVirtualTable(bool ifNotExists, const QString &name1, const QString &name2, const QString &name3, const QList &args) : + SqliteCreateVirtualTable() +{ + initName(name1, name2); + this->ifNotExistsKw = ifNotExists; + module = name3; + this->args = args; +} + +SqliteStatement*SqliteCreateVirtualTable::clone() +{ + return new SqliteCreateVirtualTable(*this); +} + +QStringList SqliteCreateVirtualTable::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCreateVirtualTable::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateVirtualTable::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteCreateVirtualTable::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList SqliteCreateVirtualTable::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqliteCreateVirtualTable::initName(const QString &name1, const QString &name2) +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +TokenList SqliteCreateVirtualTable::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("CREATE").withSpace().withKeyword("VIRTUAL").withSpace().withKeyword("TABLE"); + if (ifNotExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withKeyword("USING").withSpace().withOther(module, dialect); + if (!args.isEmpty()) + { + builder.withSpace(); + int i = 0; + for (const QString& arg : args) + { + if (i > 0) + builder.withOperator(",").withSpace(); + + builder.withTokens(Lexer::tokenize(arg, Dialect::Sqlite3)); + i++; + } + } + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.h new file mode 100644 index 0000000..cb2d231 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.h @@ -0,0 +1,42 @@ +#ifndef SQLITECREATEVIRTUALTABLE_H +#define SQLITECREATEVIRTUALTABLE_H + +#include "sqlitequery.h" + +#include +#include + +class API_EXPORT SqliteCreateVirtualTable : public SqliteQuery +{ + public: + SqliteCreateVirtualTable(); + SqliteCreateVirtualTable(const SqliteCreateVirtualTable& other); + SqliteCreateVirtualTable(bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3); + SqliteCreateVirtualTable(bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3, const QList& args); + + SqliteStatement* clone(); + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void initName(const QString& name1, const QString& name2); + + public: + bool ifNotExistsKw = false; + QString database = QString::null; + QString table = QString::null; + QString module = QString::null; + QList args; +}; + +typedef QSharedPointer SqliteCreateVirtualTablePtr; + +#endif // SQLITECREATEVIRTUALTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.cpp new file mode 100644 index 0000000..4ef4d51 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.cpp @@ -0,0 +1,54 @@ +#include "sqlitedeferrable.h" + +QString sqliteDeferrable(SqliteDeferrable deferrable) +{ + switch (deferrable) + { + case SqliteDeferrable::NOT_DEFERRABLE: + return "NOT DEFERRABLE"; + case SqliteDeferrable::DEFERRABLE: + return "DEFERRABLE"; + case SqliteDeferrable::null: + break; + } + return QString::null; +} + +SqliteDeferrable sqliteDeferrable(const QString& deferrable) +{ + QString upper = deferrable.toUpper(); + if (upper == "NOT DEFERRABLE") + return SqliteDeferrable::NOT_DEFERRABLE; + + if (upper == "DEFERRABLE") + return SqliteDeferrable::DEFERRABLE; + + return SqliteDeferrable::null; +} + + +QString sqliteInitially(SqliteInitially initially) +{ + switch (initially) + { + case SqliteInitially::DEFERRED: + return "DEFERRED"; + case SqliteInitially::IMMEDIATE: + return "IMMEDIATE"; + case SqliteInitially::null: + break; + } + return QString::null; +} + +SqliteInitially sqliteInitially(const QString& initially) +{ + QString upper = initially.toUpper(); + if (upper == "DEFERRED") + return SqliteInitially::DEFERRED; + + if (upper == "IMMEDIATE") + return SqliteInitially::IMMEDIATE; + + return SqliteInitially::null; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.h new file mode 100644 index 0000000..8fdf06a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.h @@ -0,0 +1,27 @@ +#ifndef SQLITEDEFERRABLE_H +#define SQLITEDEFERRABLE_H + +#include "coreSQLiteStudio_global.h" +#include + +enum class SqliteDeferrable +{ + null, + NOT_DEFERRABLE, + DEFERRABLE +}; + +enum class SqliteInitially +{ + null, + DEFERRED, + IMMEDIATE +}; + +API_EXPORT QString sqliteDeferrable(SqliteDeferrable deferrable); +API_EXPORT SqliteDeferrable sqliteDeferrable(const QString& deferrable); + +API_EXPORT QString sqliteInitially(SqliteInitially initially); +API_EXPORT SqliteInitially sqliteInitially(const QString& initially); + +#endif // SQLITEDEFERRABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.cpp new file mode 100644 index 0000000..60e5878 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.cpp @@ -0,0 +1,137 @@ +#include "sqlitedelete.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include "sqlitewith.h" + +SqliteDelete::SqliteDelete() +{ + queryType = SqliteQueryType::Delete; +} + +SqliteDelete::SqliteDelete(const SqliteDelete& other) : + SqliteQuery(other), database(other.database), table(other.table), indexedByKw(other.indexedByKw), notIndexedKw(other.notIndexedKw), + indexedBy(other.indexedBy) +{ + DEEP_COPY_FIELD(SqliteExpr, where); + DEEP_COPY_FIELD(SqliteWith, with); +} + +SqliteDelete::SqliteDelete(const QString &name1, const QString &name2, const QString& indexedByName, SqliteExpr *where, SqliteWith* with) + : SqliteDelete() +{ + init(name1, name2, where, with); + this->indexedBy = indexedByName; + this->indexedByKw = true; +} + +SqliteDelete::SqliteDelete(const QString &name1, const QString &name2, bool notIndexedKw, SqliteExpr *where, SqliteWith* with) + : SqliteDelete() +{ + init(name1, name2, where, with); + this->notIndexedKw = notIndexedKw; +} + +SqliteDelete::~SqliteDelete() +{ +} + +SqliteStatement*SqliteDelete::clone() +{ + return new SqliteDelete(*this); +} + +QStringList SqliteDelete::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteDelete::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteDelete::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteDelete::getDatabaseTokensInStatement() +{ + if (tokensMap.contains("fullname")) + return getDbTokenListFromFullname(); + + if (tokensMap.contains("nm")) + return extractPrintableTokens(tokensMap["nm"]); + + return TokenList(); +} + +QList SqliteDelete::getFullObjectsInStatement() +{ + QList result; + if (!tokensMap.contains("fullname")) + return result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqliteDelete::init(const QString &name1, const QString &name2, SqliteExpr *where, SqliteWith* with) +{ + this->where = where; + if (where) + where->setParent(this); + + this->with = with; + if (with) + with->setParent(this); + + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +TokenList SqliteDelete::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (with) + builder.withStatement(with); + + builder.withKeyword("DELETE").withSpace().withKeyword("FROM").withSpace(); + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect); + + if (indexedByKw) + builder.withSpace().withKeyword("INDEXED").withSpace().withKeyword("BY").withSpace().withOther(indexedBy, dialect); + else if (notIndexedKw) + builder.withSpace().withKeyword("NOT").withSpace().withKeyword("INDEXED"); + + if (where) + builder.withSpace().withKeyword("WHERE").withStatement(where); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.h new file mode 100644 index 0000000..90e1385 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.h @@ -0,0 +1,45 @@ +#ifndef SQLITEDELETE_H +#define SQLITEDELETE_H + +#include "sqlitequery.h" + +#include + +class SqliteExpr; +class SqliteWith; + +class API_EXPORT SqliteDelete : public SqliteQuery +{ + public: + SqliteDelete(); + SqliteDelete(const SqliteDelete& other); + SqliteDelete(const QString& name1, const QString& name2, const QString& indexedByName, SqliteExpr* where, SqliteWith* with); + SqliteDelete(const QString& name1, const QString& name2, bool notIndexedKw, SqliteExpr* where, SqliteWith* with); + ~SqliteDelete(); + + SqliteStatement* clone(); + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void init(const QString& name1, const QString& name2, SqliteExpr* where, SqliteWith* with); + + public: + QString database = QString::null; + QString table = QString::null; + bool indexedByKw = false; + bool notIndexedKw = false; + QString indexedBy = QString::null; + SqliteExpr* where = nullptr; + SqliteWith* with = nullptr; +}; + +typedef QSharedPointer SqliteDeletePtr; + +#endif // SQLITEDELETE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.cpp new file mode 100644 index 0000000..1f874d3 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.cpp @@ -0,0 +1,48 @@ +#include "sqlitedetach.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "common/global.h" +#include "parser/statementtokenbuilder.h" + +SqliteDetach::SqliteDetach() +{ + queryType = SqliteQueryType::Detach; +} + +SqliteDetach::SqliteDetach(const SqliteDetach& other) : + SqliteQuery(other), databaseKw(other.databaseKw) +{ + DEEP_COPY_FIELD(SqliteExpr, name); +} + +SqliteDetach::SqliteDetach(bool databaseKw, SqliteExpr *name) + :SqliteDetach() +{ + this->databaseKw = databaseKw; + this->name = name; + if (name) + name->setParent(this); +} + +SqliteDetach::~SqliteDetach() +{ +} + +SqliteStatement*SqliteDetach::clone() +{ + return new SqliteDetach(*this); +} + +TokenList SqliteDetach::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DETACH").withSpace(); + + if (databaseKw) + builder.withKeyword("DATABASE").withSpace(); + + builder.withStatement(name).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.h new file mode 100644 index 0000000..725cd47 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.h @@ -0,0 +1,27 @@ +#ifndef SQLITEDETACH_H +#define SQLITEDETACH_H + +#include "sqlitequery.h" + +class SqliteExpr; + +class API_EXPORT SqliteDetach : public SqliteQuery +{ + public: + SqliteDetach(); + SqliteDetach(const SqliteDetach& other); + SqliteDetach(bool databaseKw, SqliteExpr* name); + ~SqliteDetach(); + + SqliteStatement* clone(); + + bool databaseKw = false; + SqliteExpr* name = nullptr; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteDetachPtr; + +#endif // SQLITEDETACH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.cpp new file mode 100644 index 0000000..df924fe --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.cpp @@ -0,0 +1,78 @@ +#include "sqlitedropindex.h" +#include "sqlitequerytype.h" + +#include + +SqliteDropIndex::SqliteDropIndex() +{ + queryType = SqliteQueryType::DropIndex; +} + +SqliteDropIndex::SqliteDropIndex(const SqliteDropIndex& other) : + SqliteQuery(other), ifExistsKw(other.ifExistsKw), database(other.database), index(other.index) +{ +} + +SqliteDropIndex::SqliteDropIndex(bool ifExists, const QString &name1, const QString &name2) + : SqliteDropIndex() +{ + this->ifExistsKw = ifExists; + if (!name2.isNull()) + { + database = name1; + index = name2; + } + else + index = name1; +} + +SqliteStatement*SqliteDropIndex::clone() +{ + return new SqliteDropIndex(*this); +} + +QStringList SqliteDropIndex::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteDropIndex::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList SqliteDropIndex::getFullObjectsInStatement() +{ + QList result; + + // Index object + FullObject fullObj = getFullObjectFromFullname(FullObject::INDEX); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteDropIndex::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DROP").withSpace().withKeyword("INDEX").withSpace(); + + if (ifExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(index).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.h new file mode 100644 index 0000000..9ce7065 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.h @@ -0,0 +1,29 @@ +#ifndef SQLITEDROPINDEX_H +#define SQLITEDROPINDEX_H + +#include "sqlitequery.h" +#include + +class API_EXPORT SqliteDropIndex : public SqliteQuery +{ + public: + SqliteDropIndex(); + SqliteDropIndex(const SqliteDropIndex& other); + SqliteDropIndex(bool ifExistsKw, const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + bool ifExistsKw = false; + QString database = QString::null; + QString index = QString::null; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteDropIndexPtr; + +#endif // SQLITEDROPINDEX_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.cpp new file mode 100644 index 0000000..9c4aa37 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.cpp @@ -0,0 +1,86 @@ +#include "sqlitedroptable.h" +#include "sqlitequerytype.h" +#include + +SqliteDropTable::SqliteDropTable() +{ + queryType = SqliteQueryType::DropTable; +} + +SqliteDropTable::SqliteDropTable(const SqliteDropTable& other) : + SqliteQuery(other), ifExistsKw(other.ifExistsKw), database(other.database), table(other.table) +{ +} + +SqliteDropTable::SqliteDropTable(bool ifExists, const QString& name1, const QString& name2) + : SqliteDropTable() +{ + this->ifExistsKw = ifExists; + if (name2.isNull()) + this->table = name1; + else + { + this->database = name1; + this->table = name2; + } +} + +SqliteStatement* SqliteDropTable::clone() +{ + return new SqliteDropTable(*this); +} + +QStringList SqliteDropTable::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteDropTable::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteDropTable::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteDropTable::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList SqliteDropTable::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +TokenList SqliteDropTable::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DROP").withSpace().withKeyword("TABLE").withSpace(); + + if (ifExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.h new file mode 100644 index 0000000..edc4da4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.h @@ -0,0 +1,31 @@ +#ifndef SQLITEDROPTABLE_H +#define SQLITEDROPTABLE_H + +#include "sqlitequery.h" +#include + +class API_EXPORT SqliteDropTable : public SqliteQuery +{ + public: + SqliteDropTable(); + SqliteDropTable(const SqliteDropTable& other); + SqliteDropTable(bool ifExistsKw, const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + bool ifExistsKw = false; + QString database = QString::null; + QString table = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteDropTablePtr; + +#endif // SQLITEDROPTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.cpp new file mode 100644 index 0000000..471d558 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.cpp @@ -0,0 +1,79 @@ +#include "sqlitedroptrigger.h" +#include "sqlitequerytype.h" + +#include + +SqliteDropTrigger::SqliteDropTrigger() +{ + queryType = SqliteQueryType::DropTrigger; +} + +SqliteDropTrigger::SqliteDropTrigger(const SqliteDropTrigger& other) : + SqliteQuery(other), ifExistsKw(other.ifExistsKw), database(other.database), trigger(other.trigger) +{ +} + +SqliteDropTrigger::SqliteDropTrigger(bool ifExists, const QString &name1, const QString &name2) + : SqliteDropTrigger() +{ + this->ifExistsKw = ifExists; + + if (name2.isNull()) + trigger = name1; + else + { + database = name1; + trigger = name2; + } +} + +SqliteStatement*SqliteDropTrigger::clone() +{ + return new SqliteDropTrigger(*this); +} + +QStringList SqliteDropTrigger::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteDropTrigger::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList SqliteDropTrigger::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TRIGGER); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteDropTrigger::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DROP").withSpace().withKeyword("TRIGGER").withSpace(); + + if (ifExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(trigger).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.h new file mode 100644 index 0000000..5221959 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.h @@ -0,0 +1,29 @@ +#ifndef SQLITEDROPTRIGGER_H +#define SQLITEDROPTRIGGER_H + +#include "sqlitequery.h" +#include + +class API_EXPORT SqliteDropTrigger : public SqliteQuery +{ + public: + SqliteDropTrigger(); + SqliteDropTrigger(const SqliteDropTrigger& other); + SqliteDropTrigger(bool ifExistsKw, const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + bool ifExistsKw = false; + QString database = QString::null; + QString trigger = QString::null; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteDropTriggerPtr; + +#endif // SQLITEDROPTRIGGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.cpp new file mode 100644 index 0000000..06d70db --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.cpp @@ -0,0 +1,85 @@ +#include "sqlitedropview.h" +#include "sqlitequerytype.h" + +#include + +SqliteDropView::SqliteDropView() +{ + queryType = SqliteQueryType::DropView; +} + +SqliteDropView::SqliteDropView(const SqliteDropView& other) : + SqliteQuery(other), ifExistsKw(other.ifExistsKw), database(other.database), view(other.view) +{ +} + +SqliteDropView::SqliteDropView(bool ifExists, const QString &name1, const QString &name2) + : SqliteDropView() +{ + this->ifExistsKw = ifExists; + + if (name2.isNull()) + view = name1; + else + { + database = name1; + view = name2; + } +} + +SqliteStatement*SqliteDropView::clone() +{ + return new SqliteDropView(*this); +} + +QStringList SqliteDropView::getDatabasesInStatement() +{ + if (dialect == Dialect::Sqlite2) + return QStringList(); + + return getStrListFromValue(database); +} + +TokenList SqliteDropView::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return TokenList(); + + return getDbTokenListFromFullname(); +} + +QList SqliteDropView::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::VIEW); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteDropView::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DROP").withSpace().withKeyword("VIEW").withSpace(); + + if (ifExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(view).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.h new file mode 100644 index 0000000..853ccef --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.h @@ -0,0 +1,29 @@ +#ifndef SQLITEDROPVIEW_H +#define SQLITEDROPVIEW_H + +#include "sqlitequery.h" +#include + +class API_EXPORT SqliteDropView : public SqliteQuery +{ + public: + SqliteDropView(); + SqliteDropView(const SqliteDropView& other); + SqliteDropView(bool ifExistsKw, const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + bool ifExistsKw = false; + QString database = QString::null; + QString view = QString::null; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteDropViewPtr; + +#endif // SQLITEDROPVIEW_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.cpp new file mode 100644 index 0000000..628aea5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.cpp @@ -0,0 +1,27 @@ +#include "sqliteemptyquery.h" +#include "sqlitequerytype.h" +#include "parser/statementtokenbuilder.h" +#include "common/unused.h" + +SqliteEmptyQuery::SqliteEmptyQuery() +{ + queryType = SqliteQueryType::EMPTY; +} + +SqliteEmptyQuery::SqliteEmptyQuery(const SqliteEmptyQuery& other) : + SqliteEmptyQuery() +{ + UNUSED(other); +} + +SqliteStatement*SqliteEmptyQuery::clone() +{ + return new SqliteEmptyQuery(*this); +} + +TokenList SqliteEmptyQuery::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.h new file mode 100644 index 0000000..3380532 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.h @@ -0,0 +1,19 @@ +#ifndef SQLITEEMPTYQUERY_H +#define SQLITEEMPTYQUERY_H + +#include "sqlitequery.h" + +class API_EXPORT SqliteEmptyQuery : public SqliteQuery +{ + public: + SqliteEmptyQuery(); + SqliteEmptyQuery(const SqliteEmptyQuery& other); + + SqliteStatement* clone(); + + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteEmptyQueryPtr; + +#endif // SQLITEEMPTYQUERY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp new file mode 100644 index 0000000..969f029 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp @@ -0,0 +1,644 @@ +#include "sqliteexpr.h" +#include "sqliteraise.h" +#include "sqliteselect.h" +#include "sqlitecolumntype.h" +#include "parser/statementtokenbuilder.h" +#include "common/utils_sql.h" +#include "common/global.h" +#include + +SqliteExpr::SqliteExpr() +{ +} + +SqliteExpr::SqliteExpr(const SqliteExpr& other) : + SqliteStatement(other), + mode(other.mode), literalValue(other.literalValue), literalNull(other.literalNull), bindParam(other.bindParam), database(other.database), table(other.table), + column(other.column), unaryOp(other.unaryOp), binaryOp(other.binaryOp), function(other.function), collation(other.collation), + ctime(other.ctime), distinctKw(other.distinctKw), allKw(other.allKw), star(other.star), notKw(other.notKw), like(other.like), + notNull(other.notNull), possibleDoubleQuotedString(other.possibleDoubleQuotedString) +{ + DEEP_COPY_FIELD(SqliteColumnType, columnType); + DEEP_COPY_FIELD(SqliteExpr, expr1); + DEEP_COPY_FIELD(SqliteExpr, expr2); + DEEP_COPY_FIELD(SqliteExpr, expr3); + DEEP_COPY_COLLECTION(SqliteExpr, exprList); + DEEP_COPY_FIELD(SqliteSelect, select); + DEEP_COPY_FIELD(SqliteRaise, raiseFunction); +} + +SqliteExpr::~SqliteExpr() +{ +} + +SqliteExpr::LikeOp SqliteExpr::likeOp(const QString &value) +{ + QString upper = value.toUpper(); + if (upper == "LIKE") + return SqliteExpr::LikeOp::LIKE; + else if (upper == "GLOB") + return SqliteExpr::LikeOp::GLOB; + else if (upper == "REGEXP") + return SqliteExpr::LikeOp::REGEXP; + else if (upper == "MATCH") + return SqliteExpr::LikeOp::MATCH; + else + return SqliteExpr::LikeOp::null; +} + +QString SqliteExpr::likeOp(SqliteExpr::LikeOp value) +{ + switch (value) + { + case SqliteExpr::LikeOp::LIKE: + return "LIKE"; + case SqliteExpr::LikeOp::GLOB: + return "GLOB"; + case SqliteExpr::LikeOp::REGEXP: + return "REGEXP"; + case SqliteExpr::LikeOp::MATCH: + return "MATCH"; + default: + return QString::null; + } +} + +SqliteExpr::NotNull SqliteExpr::notNullOp(const QString &value) +{ + if (value == "ISNULL") + return SqliteExpr::NotNull::ISNULL; + else if (value == "NOTNULL") + return SqliteExpr::NotNull::NOTNULL; + else if (value == "NOT NULL") + return SqliteExpr::NotNull::NOT_NULL; + else + return SqliteExpr::NotNull::null; +} + +QString SqliteExpr::notNullOp(SqliteExpr::NotNull value) +{ + switch (value) + { + case SqliteExpr::NotNull::ISNULL: + return "ISNULL"; + case SqliteExpr::NotNull::NOT_NULL: + return "NOT NULL"; + case SqliteExpr::NotNull::NOTNULL: + return "NOTNULL"; + default: + return QString::null; + } +} + +SqliteStatement*SqliteExpr::clone() +{ + return new SqliteExpr(*this); +} + +void SqliteExpr::initLiteral(const QVariant &value) +{ + mode = SqliteExpr::Mode::LITERAL_VALUE; + if (value.isNull()) + initNull(); + + literalValue = value; +} + +void SqliteExpr::initNull() +{ + literalNull = true; +} + +void SqliteExpr::initCTime(const QString &name) +{ + mode = SqliteExpr::Mode::CTIME; + ctime = name; +} + +void SqliteExpr::initSubExpr(SqliteExpr *expr) +{ + mode = SqliteExpr::Mode::SUB_EXPR; + expr1 = expr; + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initBindParam(const QString& value) +{ + mode = SqliteExpr::Mode::BIND_PARAM; + bindParam = value; +} + +void SqliteExpr::initCollate(SqliteExpr *expr, const QString& value) +{ + mode = SqliteExpr::Mode::COLLATE; + expr1 = expr; + collation = value; + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initCast(SqliteExpr *expr, SqliteColumnType *type) +{ + mode = SqliteExpr::Mode::CAST; + expr1 = expr; + columnType = type; + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initFunction(const QString& fnName, int distinct, const QList& exprList) +{ + mode = SqliteExpr::Mode::FUNCTION; + function = fnName; + this->exprList = exprList; + if (distinct == 1) + distinctKw = true; + else if (distinct == 2) + allKw = true; + + foreach (SqliteExpr* expr, exprList) + expr->setParent(this); +} + +void SqliteExpr::initFunction(const QString& fnName, bool star) +{ + mode = SqliteExpr::Mode::FUNCTION; + function = fnName; + this->star = star; +} + +void SqliteExpr::initBinOp(SqliteExpr *expr1, const QString& op, SqliteExpr *expr2) +{ + mode = SqliteExpr::Mode::BINARY_OP; + this->expr1 = expr1; + this->expr2 = expr2; + binaryOp = op; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); +} + +void SqliteExpr::initUnaryOp(SqliteExpr *expr, const QString& op) +{ + mode = SqliteExpr::Mode::UNARY_OP; + expr1 = expr; + unaryOp = op; + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initLike(SqliteExpr *expr1, bool notKw, LikeOp likeOp, SqliteExpr *expr2, SqliteExpr *expr3) +{ + mode = SqliteExpr::Mode::LIKE; + this->expr1 = expr1; + this->expr2 = expr2; + this->expr3 = expr3; + this->notKw = notKw; + this->like = likeOp; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); + + if (expr3) + expr3->setParent(this); +} + +void SqliteExpr::initNull(SqliteExpr *expr, const QString& value) +{ + mode = SqliteExpr::Mode::NOTNULL; + expr1 = expr; + notNull = notNullOp(value); + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initIs(SqliteExpr *expr1, bool notKw, SqliteExpr *expr2) +{ + mode = SqliteExpr::Mode::IS; + this->expr1 = expr1; + this->notKw = notKw; + this->expr2 = expr2; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); +} + +void SqliteExpr::initBetween(SqliteExpr *expr1, bool notKw, SqliteExpr *expr2, SqliteExpr *expr3) +{ + mode = SqliteExpr::Mode::BETWEEN; + this->expr1 = expr1; + this->expr2 = expr2; + this->expr3 = expr3; + this->notKw = notKw; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); + + if (expr3) + expr3->setParent(this); +} + +void SqliteExpr::initIn(SqliteExpr *expr, bool notKw, const QList& exprList) +{ + mode = SqliteExpr::Mode::IN; + expr1 = expr; + this->notKw = notKw; + this->exprList = exprList; + foreach (SqliteExpr* expr, exprList) + expr->setParent(this); +} + +void SqliteExpr::initIn(SqliteExpr *expr, bool notKw, SqliteSelect *select) +{ + mode = SqliteExpr::Mode::IN; + expr1 = expr; + this->notKw = notKw; + this->select = select; + if (expr) + expr->setParent(this); + + if (select) + select->setParent(this); +} + +void SqliteExpr::initIn(SqliteExpr *expr, bool notKw, const QString& name1, const QString& name2) +{ + mode = SqliteExpr::Mode::IN; + expr1 = expr; + this->notKw = notKw; + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; + + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initExists(SqliteSelect *select) +{ + mode = SqliteExpr::Mode::EXISTS; + this->select = select; + if (select) + select->setParent(this); +} + +void SqliteExpr::initSubSelect(SqliteSelect *select) +{ + mode = SqliteExpr::Mode::SUB_SELECT; + this->select = select; + if (select) + select->setParent(this); +} + +void SqliteExpr::initCase(SqliteExpr *expr1, const QList& exprList, SqliteExpr *expr2) +{ + mode = SqliteExpr::Mode::CASE; + this->expr1 = expr1; + this->expr2 = expr2; + this->exprList = exprList; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); + + foreach (SqliteExpr* expr, exprList) + expr->setParent(this); +} + +void SqliteExpr::initRaise(const QString& type, const QString& text) +{ + mode = SqliteExpr::Mode::RAISE; + raiseFunction = new SqliteRaise(type, text); +} + +QStringList SqliteExpr::getColumnsInStatement() +{ + return getStrListFromValue(column); +} + +QStringList SqliteExpr::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteExpr::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteExpr::getColumnTokensInStatement() +{ + TokenList list; + if (!column.isNull()) + { + if (!table.isNull()) + { + if (!database.isNull()) + list << tokens[4]; + else + list << tokens[2]; + } + else + list << tokens[0]; + } + return list; +} + +TokenList SqliteExpr::getTableTokensInStatement() +{ + TokenList list; + if (!table.isNull()) + { + if (!database.isNull()) + list << tokens[2]; + else + list << tokens[0]; + } + + return list; +} + +TokenList SqliteExpr::getDatabaseTokensInStatement() +{ + TokenList list; + if (!database.isNull()) + list << tokens[0]; + + return list; +} + +QList SqliteExpr::getFullObjectsInStatement() +{ + QList result; + if (mode != Mode::ID) + return result; + + if (!table.isNull()) + { + if (!database.isNull()) + { + FullObject dbFullObject = getDbFullObject(tokens[0]); + result << dbFullObject; + dbTokenForFullObjects = dbFullObject.database; + + result << getFullObject(FullObject::TABLE, dbTokenForFullObjects, tokens[2]); + } + else + result << getFullObject(FullObject::TABLE, dbTokenForFullObjects, tokens[0]); + } + + return result; +} + +void SqliteExpr::initId(const QString &db, const QString &table, const QString &column) +{ + mode = SqliteExpr::Mode::ID; + database = db; + this->table = table; + this->column = column; +} + +void SqliteExpr::initId(const QString& table, const QString& column) +{ + mode = SqliteExpr::Mode::ID; + this->table = table; + this->column = column; +} + +void SqliteExpr::initId(const QString& column) +{ + mode = SqliteExpr::Mode::ID; + this->column = column; +} + +TokenList SqliteExpr::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + switch (mode) + { + case SqliteExpr::Mode::null: + break; + case SqliteExpr::Mode::LITERAL_VALUE: + { + if (literalNull) + builder.withKeyword("NULL"); + else + builder.withLiteralValue(literalValue); + break; + } + case SqliteExpr::Mode::CTIME: + builder.withKeyword(ctime.toUpper()); + break; + case SqliteExpr::Mode::BIND_PARAM: + builder.withBindParam(bindParam); + break; + case SqliteExpr::Mode::ID: + builder.withTokens(rebuildId()); + break; + case SqliteExpr::Mode::UNARY_OP: + builder.withOperator(unaryOp).withSpace().withStatement(expr1); + break; + case SqliteExpr::Mode::BINARY_OP: + builder.withStatement(expr1).withSpace().withOperator(binaryOp).withSpace().withStatement(expr2); + break; + case SqliteExpr::Mode::FUNCTION: + builder.withOther(function).withParLeft().withStatementList(exprList).withParRight(); + break; + case SqliteExpr::Mode::SUB_EXPR: + builder.withParLeft().withStatement(expr1).withParRight(); + break; + case SqliteExpr::Mode::CAST: + builder.withKeyword("CAST").withSpace().withParLeft().withStatement(expr1).withSpace().withKeyword("AS") + .withStatement(columnType).withParRight(); + break; + case SqliteExpr::Mode::COLLATE: + builder.withStatement(expr1).withSpace().withKeyword("COLLATE").withSpace().withOther(collation, dialect); + break; + case SqliteExpr::Mode::LIKE: + builder.withTokens(rebuildLike()); + break; + case SqliteExpr::Mode::NULL_: + builder.withKeyword("NULL"); + break; + case SqliteExpr::Mode::NOTNULL: + builder.withTokens(rebuildNotNull()); + break; + case SqliteExpr::Mode::IS: + builder.withTokens(rebuildIs()); + break; + case SqliteExpr::Mode::BETWEEN: + builder.withTokens(rebuildBetween()); + break; + case SqliteExpr::Mode::IN: + builder.withTokens(rebuildIn()); + break; + case SqliteExpr::Mode::EXISTS: + builder.withKeyword("EXISTS").withParLeft().withStatement(select).withParRight(); + break; + case SqliteExpr::Mode::CASE: + builder.withTokens(rebuildCase()); + break; + case SqliteExpr::Mode::SUB_SELECT: + builder.withParLeft().withStatement(select).withParRight(); + break; + case SqliteExpr::Mode::RAISE: + builder.withStatement(raiseFunction); + break; + } + + return builder.build(); +} + +void SqliteExpr::evaluatePostParsing() +{ + if (tokens.size() > 0) + { + QString val = tokens.first()->value; + if (val[0] == '"' && val[0] == val[val.length() - 1]) + possibleDoubleQuotedString = true; + } +} + +TokenList SqliteExpr::rebuildId() +{ + StatementTokenBuilder builder; + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + if (!table.isNull()) + builder.withOther(table, dialect).withOperator("."); + + if (possibleDoubleQuotedString) + builder.withStringPossiblyOther(column, dialect); + else + builder.withOther(column, dialect); + + return builder.build(); +} + +TokenList SqliteExpr::rebuildLike() +{ + StatementTokenBuilder builder; + builder.withStatement(expr1).withSpace(); + if (notKw) + builder.withKeyword("NOT").withSpace(); + + builder.withKeyword(likeOp(like)).withSpace().withStatement(expr2); + if (expr3) + builder.withSpace().withKeyword("ESCAPE").withStatement(expr3); + + return builder.build(); +} + +TokenList SqliteExpr::rebuildNotNull() +{ + StatementTokenBuilder builder; + switch (notNull) + { + case SqliteExpr::NotNull::ISNULL: + builder.withKeyword("ISNULL"); + break; + case SqliteExpr::NotNull::NOT_NULL: + builder.withKeyword("NOT").withSpace().withKeyword("NULL"); + break; + case SqliteExpr::NotNull::NOTNULL: + builder.withKeyword("NOTNULL"); + break; + case SqliteExpr::NotNull::null: + break; + } + return builder.build(); +} + +TokenList SqliteExpr::rebuildIs() +{ + StatementTokenBuilder builder; + builder.withStatement(expr1).withSpace().withKeyword("IS"); + if (notKw) + builder.withSpace().withKeyword("NOT"); + + builder.withStatement(expr2); + return builder.build(); +} + +TokenList SqliteExpr::rebuildBetween() +{ + StatementTokenBuilder builder; + builder.withStatement(expr1); + + if (notKw) + builder.withSpace().withKeyword("NOT"); + + builder.withSpace().withKeyword("BETWEEN").withStatement(expr2).withSpace().withKeyword("AND").withStatement(expr3); + return builder.build(); +} + +TokenList SqliteExpr::rebuildIn() +{ + StatementTokenBuilder builder; + builder.withStatement(expr1); + + if (notKw) + builder.withSpace().withKeyword("NOT"); + + builder.withSpace().withKeyword("IN").withSpace(); + if (select) + { + builder.withParLeft().withStatement(select).withParRight(); + } + else if (exprList.size() > 0) + { + builder.withParLeft().withStatementList(exprList).withParRight(); + } + else + { + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect); + } + return builder.build(); +} + +TokenList SqliteExpr::rebuildCase() +{ + StatementTokenBuilder builder; + builder.withKeyword("CASE"); + if (expr1) + builder.withStatement(expr1); + + builder.withSpace(); + + bool then = false; + foreach (SqliteExpr* expr, exprList) + { + if (then) + builder.withKeyword("THEN"); + else + builder.withKeyword("WHEN"); + + builder.withStatement(expr).withSpace(); + then = !then; + } + + if (expr2) + builder.withKeyword("ELSE").withStatement(expr2).withSpace(); + + builder.withKeyword("END"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.h new file mode 100644 index 0000000..f57004f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.h @@ -0,0 +1,144 @@ +#ifndef SQLITEEXPR_H +#define SQLITEEXPR_H + +#include "sqlitestatement.h" +#include +#include +#include + +class SqliteSelect; +class SqliteColumnType; +class SqliteRaise; + +class API_EXPORT SqliteExpr : public SqliteStatement +{ + public: + enum class Mode + { + null, + LITERAL_VALUE, + CTIME, + BIND_PARAM, + ID, + UNARY_OP, + BINARY_OP, + FUNCTION, + SUB_EXPR, + CAST, + COLLATE, // in Sqlite2 exists only in expr of sortlist + LIKE, + NULL_, + NOTNULL, + IS, + BETWEEN, + IN, + EXISTS, + CASE, + SUB_SELECT, + RAISE + }; + + enum class NotNull + { + ISNULL, + NOT_NULL, + NOTNULL, + null + }; + + enum class LikeOp + { + LIKE, + GLOB, + REGEXP, + MATCH, + null + }; + + SqliteExpr(); + SqliteExpr(const SqliteExpr& other); + ~SqliteExpr(); + + static LikeOp likeOp(const QString& value); + static QString likeOp(LikeOp value); + static NotNull notNullOp(const QString& value); + static QString notNullOp(NotNull value); + + SqliteStatement* clone(); + void initLiteral(const QVariant& value); + void initNull(); + void initCTime(const QString& name); + void initSubExpr(SqliteExpr* expr); + void initId(const QString& db, const QString& table, const QString& column); + void initId(const QString& table, const QString& column); + void initId(const QString& column); + void initBindParam(const QString& value); + void initCollate(SqliteExpr* expr, const QString& value); + void initCast(SqliteExpr* expr, SqliteColumnType* type); + void initFunction(const QString& fnName, int distinct, const QList& exprList); + void initFunction(const QString& fnName, bool star = false); + void initBinOp(SqliteExpr* expr1, const QString& op, SqliteExpr* expr2); + void initUnaryOp(SqliteExpr* expr, const QString& op); + void initLike(SqliteExpr* expr1, bool notKw, SqliteExpr::LikeOp likeOp, SqliteExpr* expr2, SqliteExpr* expr3 = nullptr); + void initNull(SqliteExpr* expr, const QString& value); + void initIs(SqliteExpr* expr1, bool notKw, SqliteExpr* expr2); + void initBetween(SqliteExpr* expr1, bool notKw, SqliteExpr* expr2, SqliteExpr* expr3); + void initIn(SqliteExpr* expr, bool notKw, const QList& exprList); + void initIn(SqliteExpr* expr, bool notKw, SqliteSelect* select); + void initIn(SqliteExpr* expr, bool notKw, const QString& name1, const QString& name2); + void initExists(SqliteSelect* select); + void initSubSelect(SqliteSelect* select); + void initCase(SqliteExpr* expr1, const QList& exprList, SqliteExpr* expr2); + void initRaise(const QString& type, const QString& text = QString::null); + + Mode mode = Mode::null; + QVariant literalValue = QVariant(); + bool literalNull = false; + QString bindParam = QString::null; + QString database = QString::null; + QString table = QString::null; + QString column = QString::null; + QString unaryOp = QString::null; + QString binaryOp = QString::null; + QString function = QString::null; + QString collation = QString::null; + QString ctime = QString::null; + SqliteColumnType* columnType = nullptr; + SqliteExpr* expr1 = nullptr; + SqliteExpr* expr2 = nullptr; + SqliteExpr* expr3 = nullptr; + QList exprList; + SqliteSelect* select = nullptr; + bool distinctKw = false; + bool allKw = false; // alias for DISTINCT as for sqlite3 grammar + bool star = false; + bool notKw = false; + LikeOp like = LikeOp::null; + NotNull notNull = NotNull::null; + SqliteRaise* raiseFunction = nullptr; + bool possibleDoubleQuotedString = false; + + protected: + QStringList getColumnsInStatement(); + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getColumnTokensInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + void evaluatePostParsing(); + + private: + TokenList rebuildId(); + TokenList rebuildLike(); + TokenList rebuildNotNull(); + TokenList rebuildIs(); + TokenList rebuildBetween(); + TokenList rebuildIn(); + TokenList rebuildCase(); +}; + +typedef QSharedPointer SqliteExprPtr; + +#endif // SQLITEEXPR_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.cpp new file mode 100644 index 0000000..9a29db2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.cpp @@ -0,0 +1,187 @@ +#include "sqliteforeignkey.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include + +SqliteForeignKey::Condition::Condition(SqliteForeignKey::Condition::Action action, SqliteForeignKey::Condition::Reaction reaction) +{ + this->action = action; + this->reaction = reaction; +} + +SqliteForeignKey::Condition::Condition(const QString &name) +{ + this->action = SqliteForeignKey::Condition::MATCH; + this->name = name; +} + +SqliteForeignKey::Condition::Condition(const SqliteForeignKey::Condition& other) : + SqliteStatement(other), action(other.action), name(other.name), reaction(other.reaction) +{ +} + +QString SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::Reaction reaction) +{ + switch (reaction) + { + case SqliteForeignKey::Condition::SET_NULL: + return "SET NULL"; + case SqliteForeignKey::Condition::SET_DEFAULT: + return "SET DEFAULT"; + case SqliteForeignKey::Condition::CASCADE: + return "CASCADE"; + case SqliteForeignKey::Condition::RESTRICT: + return "RESTRICT"; + case SqliteForeignKey::Condition::NO_ACTION: + return "NO ACTION"; + } + return QString::null; +} + +SqliteForeignKey::Condition::Reaction SqliteForeignKey::Condition::toEnum(const QString& reaction) +{ + QString upper = reaction.toUpper(); + if (upper == "SET NULL") + return SET_NULL; + + if (upper == "SET DEFAULT") + return SET_DEFAULT; + + if (upper == "CASCADE") + return CASCADE; + + if (upper == "RESTRICT") + return RESTRICT; + + if (upper == "NO ACTION") + return NO_ACTION; + + qCritical() << "Unknown Reaction value. Cannot convert to Condition::Reaction. Returning default, the SET_NULL."; + return SET_NULL; +} + +SqliteStatement*SqliteForeignKey::Condition::clone() +{ + return new SqliteForeignKey::Condition(*this); +} + +SqliteForeignKey::SqliteForeignKey() +{ +} + +SqliteForeignKey::SqliteForeignKey(const SqliteForeignKey& other) : + SqliteStatement(other), foreignTable(other.foreignTable), deferrable(other.deferrable), initially(other.initially) +{ + DEEP_COPY_COLLECTION(SqliteIndexedColumn, indexedColumns); + DEEP_COPY_COLLECTION(Condition, conditions); +} + +SqliteForeignKey::~SqliteForeignKey() +{ +} + +SqliteStatement*SqliteForeignKey::clone() +{ + return new SqliteForeignKey(*this); +} + +QStringList SqliteForeignKey::getTablesInStatement() +{ + return getStrListFromValue(foreignTable); +} + +TokenList SqliteForeignKey::getTableTokensInStatement() +{ + return parentStatement()->getContextTableTokens(false, false); +} + +QList SqliteForeignKey::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj; + TokenList tokens = getTableTokensInStatement(); + if (tokens.size() > 0) + fullObj = getFullObject(FullObject::TABLE, dbTokenForFullObjects, tokens[0]); + + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +TokenList SqliteForeignKey::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("REFERENCES").withSpace().withOther(foreignTable, dialect); + + if (indexedColumns.size() > 0) + builder.withSpace().withParLeft().withStatementList(indexedColumns).withParRight(); + + if (conditions.size() > 0) + builder.withSpace().withStatementList(conditions, ""); + + if (deferrable != SqliteDeferrable::null) + { + if (deferrable == SqliteDeferrable::NOT_DEFERRABLE) + builder.withSpace().withKeyword("NOT").withSpace().withKeyword("DEFERRABLE"); + else if (deferrable == SqliteDeferrable::DEFERRABLE) + builder.withSpace().withKeyword("DEFERRABLE"); + + if (initially != SqliteInitially::null) + builder.withSpace().withKeyword("INITIALLY").withSpace().withKeyword(sqliteInitially(initially)); + } + + return builder.build(); +} + + +TokenList SqliteForeignKey::Condition::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + switch (action) + { + case SqliteForeignKey::Condition::UPDATE: + builder.withKeyword("ON").withSpace().withKeyword("UPDATE").withSpace(); + applyReactionToBuilder(builder); + break; + case SqliteForeignKey::Condition::INSERT: + builder.withKeyword("ON").withSpace().withKeyword("INSERT").withSpace(); + applyReactionToBuilder(builder); + break; + case SqliteForeignKey::Condition::DELETE: + builder.withKeyword("ON").withSpace().withKeyword("DELETE").withSpace(); + applyReactionToBuilder(builder); + break; + case SqliteForeignKey::Condition::MATCH: + builder.withKeyword("MATCH").withSpace().withOther(name); + break; + } + + return builder.build(); +} + +void SqliteForeignKey::Condition::applyReactionToBuilder(StatementTokenBuilder& builder) +{ + switch (reaction) + { + case SqliteForeignKey::Condition::SET_NULL: + builder.withKeyword("SET").withSpace().withKeyword("NULL"); + break; + case SqliteForeignKey::Condition::SET_DEFAULT: + builder.withKeyword("SET").withSpace().withKeyword("DEFAULT"); + break; + case SqliteForeignKey::Condition::CASCADE: + builder.withKeyword("CASCADE"); + break; + case SqliteForeignKey::Condition::RESTRICT: + builder.withKeyword("RESTRICT"); + break; + case SqliteForeignKey::Condition::NO_ACTION: + builder.withKeyword("NO").withSpace().withKeyword("ACTION"); + break; + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.h new file mode 100644 index 0000000..18e0bcb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.h @@ -0,0 +1,75 @@ +#ifndef SQLITEFOREIGNKEY_H +#define SQLITEFOREIGNKEY_H + +#include "sqlitestatement.h" +#include "sqliteindexedcolumn.h" +#include "sqlitedeferrable.h" +#include "parser/statementtokenbuilder.h" +#include +#include + +class API_EXPORT SqliteForeignKey : public SqliteStatement +{ + public: + class API_EXPORT Condition : public SqliteStatement + { + public: + enum Action + { + UPDATE, + INSERT, + DELETE, + MATCH + }; + + enum Reaction + { + SET_NULL, + SET_DEFAULT, + CASCADE, + RESTRICT, + NO_ACTION + }; + + Condition(Action action, Reaction reaction); + explicit Condition(const QString& name); + Condition(const Condition& other); + + static QString toString(Reaction reaction); + static Reaction toEnum(const QString& reaction); + + SqliteStatement* clone(); + + Action action; + QString name = QString::null; + Reaction reaction = NO_ACTION; + + protected: + TokenList rebuildTokensFromContents(); + + private: + void applyReactionToBuilder(StatementTokenBuilder& builder); + }; + + SqliteForeignKey(); + SqliteForeignKey(const SqliteForeignKey& other); + ~SqliteForeignKey(); + + SqliteStatement* clone(); + + QString foreignTable = QString::null; + QList indexedColumns; + QList conditions; + SqliteDeferrable deferrable = SqliteDeferrable::null; // Those two are for table constraint only, + SqliteInitially initially = SqliteInitially::null; // because column has its own fields for that. + + protected: + QStringList getTablesInStatement(); + TokenList getTableTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteForeignKeyPtr; + +#endif // SQLITEFOREIGNKEY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.cpp new file mode 100644 index 0000000..5e65eab --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.cpp @@ -0,0 +1,52 @@ +#include "sqliteindexedcolumn.h" +#include "parser/statementtokenbuilder.h" + +SqliteIndexedColumn::SqliteIndexedColumn() +{ +} + +SqliteIndexedColumn::SqliteIndexedColumn(const SqliteIndexedColumn& other) : + SqliteStatement(other), name(other.name), sortOrder(other.sortOrder), collate(other.collate) +{ +} + +SqliteIndexedColumn::SqliteIndexedColumn(const QString &name, const QString &collate, SqliteSortOrder sortOrder) + : SqliteIndexedColumn() +{ + this->name = name; + this->sortOrder = sortOrder; + this->collate = collate; +} + +SqliteIndexedColumn::SqliteIndexedColumn(const QString& name) + : SqliteIndexedColumn() +{ + this->name = name; +} + +SqliteStatement*SqliteIndexedColumn::clone() +{ + return new SqliteIndexedColumn(*this); +} + +QStringList SqliteIndexedColumn::getColumnsInStatement() +{ + return getStrListFromValue(name); +} + +TokenList SqliteIndexedColumn::getColumnTokensInStatement() +{ + return getTokenListFromNamedKey("nm"); +} + + +TokenList SqliteIndexedColumn::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withOther(name, dialect); + if (!collate.isNull()) + builder.withSpace().withKeyword("COLLATE").withSpace().withOther(collate, dialect); + + builder.withSortOrder(sortOrder); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.h new file mode 100644 index 0000000..c013d17 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.h @@ -0,0 +1,30 @@ +#ifndef SQLITEINDEXEDCOLUMN_H +#define SQLITEINDEXEDCOLUMN_H + +#include "sqlitestatement.h" +#include "sqlitesortorder.h" +#include + +class API_EXPORT SqliteIndexedColumn : public SqliteStatement +{ + public: + SqliteIndexedColumn(); + SqliteIndexedColumn(const SqliteIndexedColumn& other); + SqliteIndexedColumn(const QString& name, const QString& collate, SqliteSortOrder sortOrder); + explicit SqliteIndexedColumn(const QString& name); + + SqliteStatement* clone(); + + QString name = QString::null; + SqliteSortOrder sortOrder = SqliteSortOrder::null; + QString collate = QString::null; + + protected: + QStringList getColumnsInStatement(); + TokenList getColumnTokensInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteIndexedColumnPtr; + +#endif // SQLITEINDEXEDCOLUMN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.cpp new file mode 100644 index 0000000..6c26e8d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.cpp @@ -0,0 +1,214 @@ +#include "sqliteinsert.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "sqliteselect.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include "sqlitewith.h" + +SqliteInsert::SqliteInsert() +{ + queryType = SqliteQueryType::Insert; +} + +SqliteInsert::SqliteInsert(const SqliteInsert& other) : + SqliteQuery(other), replaceKw(other.replaceKw), defaultValuesKw(other.defaultValuesKw), onConflict(other.onConflict), database(other.database), + table(other.table), columnNames(other.columnNames) +{ + DEEP_COPY_COLLECTION(SqliteExpr, values); + DEEP_COPY_FIELD(SqliteSelect, select); + DEEP_COPY_FIELD(SqliteWith, with); +} + +SqliteInsert::SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, const QList &columns, + const QList &row, SqliteWith* with) : + SqliteInsert() +{ + initName(name1, name2); + initMode(replace, onConflict); + columnNames = columns; + values = row; + + this->with = with; + if (with) + with->setParent(this); + + foreach (SqliteExpr* expr, row) + expr->setParent(this); +} + +SqliteInsert::SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, const QList &columns, + SqliteSelect *select, SqliteWith* with) : + SqliteInsert() +{ + initName(name1, name2); + initMode(replace, onConflict); + + this->with = with; + if (with) + with->setParent(this); + + columnNames = columns; + this->select = select; + if (select) + select->setParent(this); +} + +SqliteInsert::SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, const QList &columns, + SqliteWith* with) : + SqliteInsert() +{ + initName(name1, name2); + initMode(replace, onConflict); + + this->with = with; + if (with) + with->setParent(this); + + columnNames = columns; + defaultValuesKw = true; +} + +SqliteInsert::~SqliteInsert() +{ +} + +SqliteStatement*SqliteInsert::clone() +{ + return new SqliteInsert(*this); +} + +QStringList SqliteInsert::getColumnsInStatement() +{ + QStringList columns; + columns += columnNames; + return columns; +} + +QStringList SqliteInsert::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteInsert::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteInsert::getColumnTokensInStatement() +{ + TokenList list; + foreach (TokenPtr token, getTokenListFromNamedKey("inscollist_opt", -1)) + { + if (token->type != Token::OTHER && token->type != Token::KEYWORD) + continue; + + list << token; + } + return list; +} + +TokenList SqliteInsert::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteInsert::getDatabaseTokensInStatement() +{ + if (tokensMap.contains("fullname")) + return getDbTokenListFromFullname(); + + if (tokensMap.contains("nm")) + return extractPrintableTokens(tokensMap["nm"]); + + return TokenList(); +} + +QList SqliteInsert::getFullObjectsInStatement() +{ + QList result; + if (!tokensMap.contains("fullname")) + return result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqliteInsert::initName(const QString& name1, const QString& name2) +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +void SqliteInsert::initMode(bool replace, SqliteConflictAlgo onConflict) +{ + replaceKw = replace; + this->onConflict = onConflict; +} + +TokenList SqliteInsert::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (with) + builder.withStatement(with); + + if (replaceKw) + { + builder.withKeyword("REPLACE").withSpace(); + } + else + { + builder.withKeyword("INSERT").withSpace(); + if (onConflict != SqliteConflictAlgo::null) + builder.withKeyword("OR").withSpace().withKeyword(sqliteConflictAlgo(onConflict)).withSpace(); + } + + builder.withKeyword("INTO").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect).withSpace(); + + if (defaultValuesKw) + { + builder.withKeyword("DEFAULT").withSpace().withKeyword("VALUES"); + } + else + { + if (columnNames.size() > 0) + builder.withParLeft().withOtherList(columnNames, dialect).withParRight().withSpace(); + + if (select) + { + builder.withStatement(select); + } + else if (dialect == Dialect::Sqlite2) // Sqlite2 uses classic single row values + { + builder.withKeyword("VALUES").withSpace().withParLeft().withStatementList(values).withParRight(); + } + } + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.h new file mode 100644 index 0000000..4287680 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.h @@ -0,0 +1,57 @@ +#ifndef SQLITEINSERT_H +#define SQLITEINSERT_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" +#include +#include + +class SqliteSelect; +class SqliteExpr; +class SqliteWith; + +class API_EXPORT SqliteInsert : public SqliteQuery +{ + public: + SqliteInsert(); + SqliteInsert(const SqliteInsert& other); + SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString& name1, + const QString& name2, const QList& columns, + const QList& row, SqliteWith* with); + SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString& name1, + const QString& name2, const QList& columns, SqliteSelect* select, SqliteWith* with); + SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString& name1, + const QString& name2, const QList& columns, SqliteWith* with); + ~SqliteInsert(); + + SqliteStatement* clone(); + + protected: + QStringList getColumnsInStatement(); + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getColumnTokensInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void initName(const QString& name1, const QString& name2); + void initMode(bool replace, SqliteConflictAlgo onConflict); + + public: + bool replaceKw = false; + bool defaultValuesKw = false; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + QString database = QString::null; + QString table = QString::null; + QStringList columnNames; + QList values; + SqliteSelect* select = nullptr; + SqliteWith* with = nullptr; +}; + +typedef QSharedPointer SqliteInsertPtr; + +#endif // SQLITEINSERT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.cpp new file mode 100644 index 0000000..c44228a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.cpp @@ -0,0 +1,79 @@ +#include "sqlitelimit.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteLimit::SqliteLimit() +{ +} + +SqliteLimit::SqliteLimit(const SqliteLimit& other) : + SqliteStatement(other) +{ + DEEP_COPY_FIELD(SqliteExpr, limit); + DEEP_COPY_FIELD(SqliteExpr, offset); +} + +SqliteLimit::SqliteLimit(SqliteExpr *expr) +{ + limit = expr; + if (expr) + expr->setParent(this); +} + +SqliteLimit::SqliteLimit(SqliteExpr *expr1, SqliteExpr *expr2, bool offsetKeyword) +{ + limit = expr1; + offset = expr2; + offsetKw = offsetKeyword; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); +} + +SqliteLimit::SqliteLimit(const QVariant &positiveInt) +{ + limit = new SqliteExpr(); + limit->initLiteral(positiveInt); + limit->setParent(this); +} + +SqliteLimit::SqliteLimit(const QVariant &positiveInt1, const QVariant &positiveInt2) +{ + limit = new SqliteExpr(); + limit->initLiteral(positiveInt1); + limit->setParent(this); + + offset = new SqliteExpr(); + offset->initLiteral(positiveInt2); + offset->setParent(this); +} + +SqliteLimit::~SqliteLimit() +{ +} + +SqliteStatement*SqliteLimit::clone() +{ + return new SqliteLimit(*this); +} + + +TokenList SqliteLimit::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("LIMIT").withStatement(limit); + if (offset) + { + if (offsetKw) + builder.withSpace().withKeyword("OFFSET"); + else + builder.withOperator(","); + + builder.withStatement(offset); + } + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.h new file mode 100644 index 0000000..284cf49 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.h @@ -0,0 +1,31 @@ +#ifndef SQLITELIMIT_H +#define SQLITELIMIT_H + +#include "sqlitestatement.h" + +class SqliteExpr; + +class API_EXPORT SqliteLimit : public SqliteStatement +{ + public: + SqliteLimit(); + SqliteLimit(const SqliteLimit& other); + explicit SqliteLimit(SqliteExpr* expr); + SqliteLimit(SqliteExpr* expr1, SqliteExpr* expr2, bool offsetKeyword); + explicit SqliteLimit(const QVariant& positiveInt); + SqliteLimit(const QVariant& positiveInt1, const QVariant& positiveInt2); + ~SqliteLimit(); + + SqliteStatement* clone(); + + SqliteExpr* limit = nullptr; + SqliteExpr* offset = nullptr; + bool offsetKw = false; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteLimitPtr; + +#endif // SQLITELIMIT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.cpp new file mode 100644 index 0000000..3bb1b44 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.cpp @@ -0,0 +1,41 @@ +#include "sqliteorderby.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteOrderBy::SqliteOrderBy() +{ +} + +SqliteOrderBy::SqliteOrderBy(const SqliteOrderBy& other) : + SqliteStatement(other), order(other.order) +{ + DEEP_COPY_FIELD(SqliteExpr, expr); +} + +SqliteOrderBy::SqliteOrderBy(SqliteExpr *expr, SqliteSortOrder order) +{ + this->expr = expr; + this->order = order; + if (expr) + expr->setParent(this); +} + +SqliteOrderBy::~SqliteOrderBy() +{ +} + +SqliteStatement*SqliteOrderBy::clone() +{ + return new SqliteOrderBy(*this); +} + +TokenList SqliteOrderBy::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withStatement(expr); + if (order != SqliteSortOrder::null) + builder.withSpace().withKeyword(sqliteSortOrder(order)); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.h new file mode 100644 index 0000000..598423d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.h @@ -0,0 +1,28 @@ +#ifndef SQLITEORDERBY_H +#define SQLITEORDERBY_H + +#include "sqlitestatement.h" +#include "sqlitesortorder.h" + +class SqliteExpr; + +class API_EXPORT SqliteOrderBy : public SqliteStatement +{ + public: + SqliteOrderBy(); + SqliteOrderBy(const SqliteOrderBy& other); + SqliteOrderBy(SqliteExpr* expr, SqliteSortOrder order); + ~SqliteOrderBy(); + + SqliteStatement* clone(); + + SqliteExpr* expr = nullptr; + SqliteSortOrder order; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteOrderByPtr; + +#endif // SQLITEORDERBY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.cpp new file mode 100644 index 0000000..0e4f056 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.cpp @@ -0,0 +1,109 @@ +#include "sqlitepragma.h" +#include "sqlitequerytype.h" + +#include + +SqlitePragma::SqlitePragma() +{ + queryType = SqliteQueryType::Pragma; +} + +SqlitePragma::SqlitePragma(const SqlitePragma& other) : + SqliteQuery(other), database(other.database), pragmaName(other.pragmaName), value(other.value), equalsOp(other.equalsOp), parenthesis(other.parenthesis) +{ +} + +SqlitePragma::SqlitePragma(const QString &name1, const QString &name2) + : SqlitePragma() +{ + initName(name1, name2); +} + +SqlitePragma::SqlitePragma(const QString &name1, const QString &name2, const QVariant& value, bool equals) + : SqlitePragma() +{ + initName(name1, name2); + this->value = value; + if (equals) + equalsOp = true; + else + parenthesis = true; +} + +SqlitePragma::SqlitePragma(const QString &name1, const QString &name2, const QString &value, bool equals) + : SqlitePragma() +{ + initName(name1, name2); + this->value = value; + if (equals) + equalsOp = true; + else + parenthesis = true; +} + +SqliteStatement*SqlitePragma::clone() +{ + return new SqlitePragma(*this); +} + +QStringList SqlitePragma::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqlitePragma::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite2 || database.isNull()) + return TokenList(); + + return getTokenListFromNamedKey("nm"); +} + +QList SqlitePragma::getFullObjectsInStatement() +{ + QList result; + if (dialect == Dialect::Sqlite2 || database.isNull()) + return result; + + // Db object + FullObject fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqlitePragma::initName(const QString &name1, const QString &name2) +{ + if (!name2.isNull()) + { + database = name1; + pragmaName = name2; + } + else + pragmaName = name1; +} + +TokenList SqlitePragma::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("PRAGMA").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(pragmaName, dialect); + + if (equalsOp) + builder.withSpace().withOperator("=").withSpace().withLiteralValue(value); + else if (parenthesis) + builder.withParLeft().withLiteralValue(value).withParRight(); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.h new file mode 100644 index 0000000..364a16f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.h @@ -0,0 +1,41 @@ +#ifndef SQLITEPRAGMA_H +#define SQLITEPRAGMA_H + +#include "sqlitequery.h" + +#include +#include + +class API_EXPORT SqlitePragma : public SqliteQuery +{ + public: + SqlitePragma(); + SqlitePragma(const SqlitePragma& other); + SqlitePragma(const QString& name1, const QString& name2); + SqlitePragma(const QString& name1, const QString& name2, const QVariant& value, + bool equals); + SqlitePragma(const QString& name1, const QString& name2, const QString& value, + bool equals); + + SqliteStatement* clone(); + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void initName(const QString& name1, const QString& name2); + + public: + QString database = QString::null; + QString pragmaName = QString::null; + QVariant value = QVariant(); + bool equalsOp = false; + bool parenthesis = false; +}; + +typedef QSharedPointer SqlitePragmaPtr; + +#endif // SQLITEPRAGMA_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.cpp new file mode 100644 index 0000000..19c3c40 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.cpp @@ -0,0 +1,51 @@ +#include "sqlitequery.h" + +SqliteQuery::SqliteQuery() +{ +} + +SqliteQuery::SqliteQuery(const SqliteQuery& other) : + SqliteStatement(other), queryType(other.queryType), explain(other.explain), queryPlan(other.queryPlan) +{ +} + +bool SqliteQuery::isReadOnly() +{ + bool readOnly = true; + switch (queryType) + { + case SqliteQueryType::EMPTY: + case SqliteQueryType::Analyze: + case SqliteQueryType::Pragma: + case SqliteQueryType::Select: + readOnly = true; + break; + case SqliteQueryType::UNDEFINED: + case SqliteQueryType::AlterTable: + case SqliteQueryType::Attach: + case SqliteQueryType::BeginTrans: + case SqliteQueryType::CommitTrans: + case SqliteQueryType::Copy: + case SqliteQueryType::CreateIndex: + case SqliteQueryType::CreateTable: + case SqliteQueryType::CreateTrigger: + case SqliteQueryType::CreateView: + case SqliteQueryType::CreateVirtualTable: + case SqliteQueryType::Delete: + case SqliteQueryType::Detach: + case SqliteQueryType::DropIndex: + case SqliteQueryType::DropTable: + case SqliteQueryType::DropTrigger: + case SqliteQueryType::DropView: + case SqliteQueryType::Insert: + case SqliteQueryType::Reindex: + case SqliteQueryType::Release: + case SqliteQueryType::Rollback: + case SqliteQueryType::Savepoint: + case SqliteQueryType::Update: + case SqliteQueryType::Vacuum: + readOnly = false; + break; + } + return readOnly; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.h new file mode 100644 index 0000000..1667dc9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.h @@ -0,0 +1,30 @@ +#ifndef SQLITEQUERY_H +#define SQLITEQUERY_H + +#include "sqlitestatement.h" +#include "sqlitequerytype.h" + +/** + * @addtogroup sqlite_statement + * @brief The SqliteQuery class + */ +class API_EXPORT SqliteQuery : public SqliteStatement +{ + public: + SqliteQuery(); + SqliteQuery(const SqliteQuery& other); + + bool isReadOnly(); + + SqliteQueryType queryType = SqliteQueryType::UNDEFINED; + + bool explain = false; + bool queryPlan = false; +}; + +/** + * Shared pointer to SqliteQuery. + */ +typedef QSharedPointer SqliteQueryPtr; + +#endif // SQLITEQUERY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.cpp new file mode 100644 index 0000000..c369e0e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.cpp @@ -0,0 +1,66 @@ +#include "sqlitequerytype.h" + +QString sqliteQueryTypeToString(const SqliteQueryType& type) +{ + switch (type) + { + case SqliteQueryType::UNDEFINED: + return "UNDEFINED"; + case SqliteQueryType::EMPTY: + return "EMPTY"; + case SqliteQueryType::AlterTable: + return "AlterTable"; + case SqliteQueryType::Analyze: + return "Analyze"; + case SqliteQueryType::Attach: + return "Attach"; + case SqliteQueryType::BeginTrans: + return "BeginTrans"; + case SqliteQueryType::CommitTrans: + return "CommitTrans"; + case SqliteQueryType::Copy: + return "Copy"; + case SqliteQueryType::CreateIndex: + return "CreateIndex"; + case SqliteQueryType::CreateTable: + return "CreateTable"; + case SqliteQueryType::CreateTrigger: + return "CreateTrigger"; + case SqliteQueryType::CreateView: + return "CreateView"; + case SqliteQueryType::CreateVirtualTable: + return "CreateVirtualTable"; + case SqliteQueryType::Delete: + return "Delete"; + case SqliteQueryType::Detach: + return "Detach"; + case SqliteQueryType::DropIndex: + return "DropIndex"; + case SqliteQueryType::DropTable: + return "DropTable"; + case SqliteQueryType::DropTrigger: + return "DropTrigger"; + case SqliteQueryType::DropView: + return "DropView"; + case SqliteQueryType::Insert: + return "Insert"; + case SqliteQueryType::Pragma: + return "Pragma"; + case SqliteQueryType::Reindex: + return "Reindex"; + case SqliteQueryType::Release: + return "Release"; + case SqliteQueryType::Rollback: + return "Rollback"; + case SqliteQueryType::Savepoint: + return "Savepoint"; + case SqliteQueryType::Select: + return "Select"; + case SqliteQueryType::Update: + return "Update"; + case SqliteQueryType::Vacuum: + return "Vacuum"; + default: + return QString::null; + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.h new file mode 100644 index 0000000..763fcfa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.h @@ -0,0 +1,41 @@ +#ifndef SQLITEQUERYTYPE_H +#define SQLITEQUERYTYPE_H + +#include "coreSQLiteStudio_global.h" +#include + +enum class SqliteQueryType +{ + UNDEFINED, + EMPTY, // still can hold comments + AlterTable, + Analyze, + Attach, + BeginTrans, + CommitTrans, + Copy, + CreateIndex, + CreateTable, + CreateTrigger, + CreateView, + CreateVirtualTable, + Delete, + Detach, + DropIndex, + DropTable, + DropTrigger, + DropView, + Insert, + Pragma, + Reindex, + Release, + Rollback, + Savepoint, + Select, + Update, + Vacuum +}; + +QString API_EXPORT sqliteQueryTypeToString(const SqliteQueryType& type); + +#endif // SQLITEQUERYTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.cpp new file mode 100644 index 0000000..b606baa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.cpp @@ -0,0 +1,70 @@ +#include "sqliteraise.h" +#include "parser/statementtokenbuilder.h" + +SqliteRaise::SqliteRaise() +{ +} + +SqliteRaise::SqliteRaise(const SqliteRaise& other) : + SqliteStatement(other), type(other.type), message(other.message) +{ +} + +SqliteRaise::SqliteRaise(const QString &type) +{ + this->type = raiseType(type); +} + +SqliteRaise::SqliteRaise(const QString &type, const QString &text) +{ + this->type = raiseType(type); + message = text; +} + +SqliteStatement*SqliteRaise::clone() +{ + return new SqliteRaise(*this); +} + +SqliteRaise::Type SqliteRaise::raiseType(const QString &value) +{ + QString upper = value.toUpper(); + if (upper == "IGNORE") + return SqliteRaise::Type::IGNORE; + else if (upper == "ROLLBACK") + return SqliteRaise::Type::ROLLBACK; + else if (upper == "ABORT") + return SqliteRaise::Type::ABORT; + else if (upper == "FAIL") + return SqliteRaise::Type::FAIL; + else + return SqliteRaise::Type::null; +} + +QString SqliteRaise::raiseType(SqliteRaise::Type value) +{ + switch (value) + { + case SqliteRaise::Type::IGNORE: + return "IGNORE"; + case SqliteRaise::Type::ROLLBACK: + return "ROLLBACK"; + case SqliteRaise::Type::ABORT: + return "ABORT"; + case SqliteRaise::Type::FAIL: + return "FAIL"; + default: + return QString::null; + } +} + +TokenList SqliteRaise::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("RAISE").withSpace().withParLeft().withKeyword(raiseType(type)); + if (type != Type::IGNORE) + builder.withOperator(",").withSpace().withString(message); + + builder.withParRight(); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.h new file mode 100644 index 0000000..1b844f8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.h @@ -0,0 +1,38 @@ +#ifndef SQLITERAISE_H +#define SQLITERAISE_H + +#include "sqlitestatement.h" +#include + +class API_EXPORT SqliteRaise : public SqliteStatement +{ + public: + enum class Type + { + IGNORE, + ROLLBACK, + ABORT, + FAIL, + null + }; + + SqliteRaise(); + SqliteRaise(const SqliteRaise& other); + explicit SqliteRaise(const QString& type); + SqliteRaise(const QString& type, const QString& text); + + SqliteStatement* clone(); + + static Type raiseType(const QString& value); + static QString raiseType(Type value); + + Type type = Type::null; + QString message = QString::null; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteRaisePtr; + +#endif // SQLITERAISE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.cpp new file mode 100644 index 0000000..7584a37 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.cpp @@ -0,0 +1,82 @@ +#include "sqlitereindex.h" +#include "sqlitequerytype.h" + +#include + +SqliteReindex::SqliteReindex() +{ + queryType = SqliteQueryType::Reindex; +} + +SqliteReindex::SqliteReindex(const SqliteReindex& other) : + SqliteQuery(other), database(other.database), table(other.table) +{ +} + +SqliteReindex::SqliteReindex(const QString& name1, const QString& name2) + : SqliteReindex() +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +SqliteStatement*SqliteReindex::clone() +{ + return new SqliteReindex(*this); +} + +QStringList SqliteReindex::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteReindex::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteReindex::getTableTokensInStatement() +{ + return getObjectTokenListFromNmDbnm(); +} + +TokenList SqliteReindex::getDatabaseTokensInStatement() +{ + return getDbTokenListFromNmDbnm(); +} + +QList SqliteReindex::getFullObjectsInStatement() +{ + QList result; + + // Table object + FullObject fullObj = getFullObjectFromNmDbnm(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +TokenList SqliteReindex::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("REINDEX"); + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.h new file mode 100644 index 0000000..642b5bf --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.h @@ -0,0 +1,31 @@ +#ifndef SQLITEREINDEX_H +#define SQLITEREINDEX_H + +#include "sqlitequery.h" + +#include + +class API_EXPORT SqliteReindex : public SqliteQuery +{ + public: + SqliteReindex(); + SqliteReindex(const SqliteReindex& other); + SqliteReindex(const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + QString database = QString::null; + QString table = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteReindexPtr; + +#endif // SQLITEREINDEX_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.cpp new file mode 100644 index 0000000..8510524 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.cpp @@ -0,0 +1,39 @@ +#include "sqliterelease.h" +#include "sqlitequerytype.h" + +#include + +SqliteRelease::SqliteRelease() +{ + queryType = SqliteQueryType::Release; +} + +SqliteRelease::SqliteRelease(const SqliteRelease& other) : + SqliteQuery(other), name(other.name), savepointKw(other.savepointKw) +{ +} + +SqliteRelease::SqliteRelease(bool savepointKw, const QString& name) + : SqliteRelease() +{ + this->name = name; + this->savepointKw = savepointKw; +} + +SqliteStatement*SqliteRelease::clone() +{ + return new SqliteRelease(*this); +} + +TokenList SqliteRelease::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("RELEASE").withSpace(); + if (savepointKw) + builder.withKeyword("SAVEPOINT").withSpace(); + + builder.withOther(name, dialect).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.h new file mode 100644 index 0000000..d115669 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.h @@ -0,0 +1,26 @@ +#ifndef SQLITERELEASE_H +#define SQLITERELEASE_H + +#include "sqlitequery.h" + +#include + +class API_EXPORT SqliteRelease : public SqliteQuery +{ + public: + SqliteRelease(); + SqliteRelease(const SqliteRelease& other); + SqliteRelease(bool savepointKw, const QString &name); + + SqliteStatement* clone(); + + QString name = QString::null; + bool savepointKw = false; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteReleasePtr; + +#endif // SQLITERELEASE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.cpp new file mode 100644 index 0000000..a13fd4c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.cpp @@ -0,0 +1,57 @@ +#include "sqliterollback.h" +#include "sqlitequerytype.h" + +#include + +SqliteRollback::SqliteRollback() +{ + queryType = SqliteQueryType::Rollback; +} + +SqliteRollback::SqliteRollback(const SqliteRollback& other) : + SqliteQuery(other), transactionKw(other.transactionKw), toKw(other.toKw), savepointKw(other.savepointKw), name(other.name) +{ +} + +SqliteRollback::SqliteRollback(bool transactionKw, const QString& name) + : SqliteRollback() +{ + this->name = name; + this->transactionKw = transactionKw; +} + +SqliteRollback::SqliteRollback(bool transactionKw, bool savePoint, const QString& name) +{ + // we ignore name from trans_opt, + // it's not officialy supported in sqlite3 + this->name = name; + this->transactionKw = transactionKw; + toKw = true; + savepointKw = savePoint; +} + +SqliteStatement*SqliteRollback::clone() +{ + return new SqliteRollback(*this); +} + +TokenList SqliteRollback::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("ROLLBACK").withSpace(); + if (transactionKw) + builder.withKeyword("TRANSACTION").withSpace(); + + if (!name.isNull()) + { + builder.withKeyword("TO").withSpace(); + if (savepointKw) + builder.withKeyword("SAVEPOINT").withSpace(); + + builder.withOther(name, dialect); + } + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.h new file mode 100644 index 0000000..1dc1574 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.h @@ -0,0 +1,28 @@ +#ifndef SQLITEROLLBACK_H +#define SQLITEROLLBACK_H + +#include "sqlitequery.h" +#include + +class API_EXPORT SqliteRollback : public SqliteQuery +{ + public: + SqliteRollback(); + SqliteRollback(const SqliteRollback& other); + SqliteRollback(bool transactionKw, const QString& name); + SqliteRollback(bool transactionKw, bool savePoint, const QString& name); + + SqliteStatement* clone(); + + bool transactionKw = false; + bool toKw = false; + bool savepointKw = false; + QString name = QString::null; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteRollPtr; + +#endif // SQLITEROLLBACK_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.cpp new file mode 100644 index 0000000..9003086 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.cpp @@ -0,0 +1,32 @@ +#include "sqlitesavepoint.h" +#include "sqlitequerytype.h" + +#include + +SqliteSavepoint::SqliteSavepoint() +{ + queryType = SqliteQueryType::Savepoint; +} + +SqliteSavepoint::SqliteSavepoint(const SqliteSavepoint& other) : + SqliteQuery(other), name(other.name) +{ +} + +SqliteSavepoint::SqliteSavepoint(const QString &name) + : SqliteSavepoint() +{ + this->name = name; +} + +SqliteStatement*SqliteSavepoint::clone() +{ + return new SqliteSavepoint(*this); +} + +TokenList SqliteSavepoint::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("SAVEPOINT").withSpace().withOther(name, dialect).withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.h new file mode 100644 index 0000000..bd75c76 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.h @@ -0,0 +1,25 @@ +#ifndef SQLITESAVEPOINT_H +#define SQLITESAVEPOINT_H + +#include "sqlitequery.h" + +#include + +class API_EXPORT SqliteSavepoint : public SqliteQuery +{ + public: + SqliteSavepoint(); + SqliteSavepoint(const SqliteSavepoint& other); + explicit SqliteSavepoint(const QString& name); + + SqliteStatement* clone(); + + QString name = QString::null; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteSavepointPtr; + +#endif // SQLITESAVEPOINT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.cpp new file mode 100644 index 0000000..5452795 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.cpp @@ -0,0 +1,783 @@ +#include "sqliteselect.h" +#include "sqlitequerytype.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include "sqlitewith.h" +#include + +SqliteSelect::SqliteSelect() +{ + queryType = SqliteQueryType::Select; +} + +SqliteSelect::SqliteSelect(const SqliteSelect& other) : + SqliteQuery(other) +{ + DEEP_COPY_COLLECTION(Core, coreSelects); + DEEP_COPY_FIELD(SqliteWith, with); +} + +SqliteSelect* SqliteSelect::append(Core* core) +{ + SqliteSelect* select = new SqliteSelect(); + select->coreSelects << core; + core->setParent(select); + return select; +} + +SqliteSelect* SqliteSelect::append(SqliteSelect* select, SqliteSelect::CompoundOperator op, Core* core) +{ + if (!select) + select = new SqliteSelect(); + + core->compoundOp = op; + select->coreSelects << core; + core->setParent(select); + return select; +} + +SqliteSelect* SqliteSelect::append(const QList>& values) +{ + return append(nullptr, CompoundOperator::null, values); +} + +SqliteSelect* SqliteSelect::append(SqliteSelect* select, SqliteSelect::CompoundOperator op, const QList>& values) +{ + if (!select) + select = new SqliteSelect(); + + bool first = true; + + Core::ResultColumn* resCol = nullptr; + QList resColList; + foreach (const QList& singleValues, values) + { + Core* core = new Core(); + core->setParent(select); + core->compoundOp = op; + core->valuesMode = true; + if (first) + { + op = CompoundOperator::UNION_ALL; + first = false; + } + select->coreSelects << core; + + resColList.clear(); + foreach (SqliteExpr* value, singleValues) + { + resCol = new Core::ResultColumn(value, false, QString::null); + resCol->rebuildTokens(); + resCol->setParent(core); + core->resultColumns << resCol; + } + } + + return select; +} + +SqliteStatement*SqliteSelect::clone() +{ + return new SqliteSelect(*this); +} + +QString SqliteSelect::compoundOperator(SqliteSelect::CompoundOperator op) +{ + switch (op) + { + case SqliteSelect::CompoundOperator::UNION: + return "UNION"; + case SqliteSelect::CompoundOperator::UNION_ALL: + return "UNION ALL"; + case SqliteSelect::CompoundOperator::INTERSECT: + return "INTERSECT"; + case SqliteSelect::CompoundOperator::EXCEPT: + return "EXCEPT"; + case SqliteSelect::CompoundOperator::null: + break; + } + return QString::null; +} + +SqliteSelect::CompoundOperator SqliteSelect::compoundOperator(const QString& op) +{ + QString upStr = op.toUpper(); + if (upStr == "UNION") + return CompoundOperator::UNION; + else if (upStr == "UNION ALL") + return CompoundOperator::UNION_ALL; + else if (upStr == "EXCEPT") + return CompoundOperator::EXCEPT; + else if (upStr == "INTERSECT") + return CompoundOperator::INTERSECT; + else + return CompoundOperator::null; +} + +void SqliteSelect::reset() +{ + foreach (Core* core, coreSelects) + delete core; + + coreSelects.clear(); +} + +void SqliteSelect::setWith(SqliteWith* with) +{ + this->with = with; + if (with) + with->setParent(this); +} + +SqliteSelect::Core::Core() +{ +} + +SqliteSelect::Core::Core(const SqliteSelect::Core& other) : + SqliteStatement(other), compoundOp(other.compoundOp), distinctKw(other.distinctKw), allKw(other.allKw) +{ + DEEP_COPY_COLLECTION(ResultColumn, resultColumns); + DEEP_COPY_FIELD(JoinSource, from); + DEEP_COPY_FIELD(SqliteExpr, where); + DEEP_COPY_FIELD(SqliteExpr, having); + DEEP_COPY_COLLECTION(SqliteExpr, groupBy); + DEEP_COPY_COLLECTION(SqliteOrderBy, orderBy); + DEEP_COPY_FIELD(SqliteLimit, limit); +} + +SqliteSelect::Core::Core(int distinct, const QList &resCols, SqliteSelect::Core::JoinSource *src, SqliteExpr *where, const QList &groupBy, SqliteExpr *having, const QList& orderBy, SqliteLimit* limit) +{ + if (distinct == 1) + distinctKw = true; + else if (distinct == 2) + allKw = true; + + from = src; + this->where = where; + this->having = having; + this->groupBy = groupBy; + resultColumns = resCols; + this->limit = limit; + this->orderBy = orderBy; + + if (from) + from->setParent(this); + + if (where) + where->setParent(this); + + if (having) + having->setParent(this); + + if (limit) + limit->setParent(this); + + foreach (SqliteOrderBy* order, orderBy) + order->setParent(this); + + foreach (SqliteExpr* expr, groupBy) + expr->setParent(this); + + foreach (SqliteSelect::Core::ResultColumn* resCol, resCols) + resCol->setParent(this); +} + +SqliteStatement*SqliteSelect::Core::clone() +{ + return new SqliteSelect::Core(*this); +} + +SqliteSelect::Core::ResultColumn::ResultColumn() +{ +} + +SqliteSelect::Core::ResultColumn::ResultColumn(const SqliteSelect::Core::ResultColumn& other) : + SqliteStatement(other), star(other.star), asKw(other.asKw), alias(other.alias), table(other.table) +{ + DEEP_COPY_FIELD(SqliteExpr, expr); +} + +SqliteSelect::Core::ResultColumn::ResultColumn(SqliteExpr *expr, bool asKw, const QString &alias) +{ + this->expr = expr; + this->asKw = asKw; + this->alias = alias; + if (expr) + expr->setParent(this); +} + +SqliteSelect::Core::ResultColumn::ResultColumn(bool star, const QString &table) +{ + this->star = star; + this->table = table; +} + +SqliteSelect::Core::ResultColumn::ResultColumn(bool star) +{ + this->star = star; +} + +bool SqliteSelect::Core::ResultColumn::isRowId() +{ + if (!expr) + return false; + + if (expr->column.isEmpty()) + return false; + + return expr->column.compare("rowid", Qt::CaseInsensitive) == 0; +} + +SqliteStatement*SqliteSelect::Core::ResultColumn::clone() +{ + return new SqliteSelect::Core::ResultColumn(*this); +} + +QStringList SqliteSelect::Core::ResultColumn::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +TokenList SqliteSelect::Core::ResultColumn::getTableTokensInStatement() +{ + // To avoid warnings about missing "nm" key + if (table.isNull()) + return TokenList(); + + // Now, we know table was specified + return getTokenListFromNamedKey("nm"); +} + +QList SqliteSelect::Core::ResultColumn::getFullObjectsInStatement() +{ + QList result; + + // Table object + TokenList tableTokens = getTableTokensInStatement(); + FullObject fullObj; + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::TABLE, dbTokenForFullObjects, tableTokens[0]); + + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +SqliteSelect::Core::SingleSource::SingleSource() +{ +} + +SqliteSelect::Core::SingleSource::SingleSource(const SqliteSelect::Core::SingleSource& other) : + SqliteStatement(other), database(other.database), table(other.table), alias(other.alias), asKw(other.asKw), indexedByKw(other.indexedByKw), + notIndexedKw(other.notIndexedKw), indexedBy(other.indexedBy) +{ + DEEP_COPY_FIELD(SqliteSelect, select); + DEEP_COPY_FIELD(JoinSource, joinSource); +} + +SqliteSelect::Core::SingleSource::SingleSource(const QString& name1, const QString& name2, bool asKw, const QString& alias, bool notIndexedKw, const QString& indexedBy) +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; + + this->asKw = asKw; + this->alias = alias; + this->indexedBy = indexedBy; + this->indexedByKw = !(indexedBy.isNull()); + this->notIndexedKw = notIndexedKw; +} + +SqliteSelect::Core::SingleSource::SingleSource(SqliteSelect *select, bool asKw, const QString &alias) +{ + this->select = select; + this->asKw = asKw; + this-> alias = alias; + if (select) + select->setParent(this); +} + +SqliteSelect::Core::SingleSource::SingleSource(JoinSource *src, bool asKw, const QString &alias) +{ + this->joinSource = src; + this->asKw = asKw; + this-> alias = alias; + if (src) + src->setParent(this); +} + +SqliteStatement*SqliteSelect::Core::SingleSource::clone() +{ + return new SqliteSelect::Core::SingleSource(*this); +} + +QStringList SqliteSelect::Core::SingleSource::getTablesInStatement() +{ + // This method returns tables only! No aliases. + // Otherwise the completion sorter won't work correctly. + // To return tables with aliases use/create other method. + return getStrListFromValue(table); +} + +QStringList SqliteSelect::Core::SingleSource::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteSelect::Core::SingleSource::getTableTokensInStatement() +{ + if (table.isNull()) + return TokenList(); + + return getObjectTokenListFromNmDbnm(); +} + +TokenList SqliteSelect::Core::SingleSource::getDatabaseTokensInStatement() +{ + if (database.isNull()) + return TokenList(); + + return getDbTokenListFromNmDbnm(); +} + +QList SqliteSelect::Core::SingleSource::getFullObjectsInStatement() +{ + QList result; + + // Table object + if (!table.isNull()) + { + FullObject fullObj = getFullObjectFromNmDbnm(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + } + + // Db object + if (!database.isNull()) + { + FullObject fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + } + + return result; +} + +SqliteSelect::Core::JoinConstraint::JoinConstraint() +{ +} + +SqliteSelect::Core::JoinConstraint::JoinConstraint(const SqliteSelect::Core::JoinConstraint& other) : + SqliteStatement(other), columnNames(other.columnNames) +{ + DEEP_COPY_FIELD(SqliteExpr, expr); +} + +SqliteSelect::Core::JoinConstraint::JoinConstraint(SqliteExpr *expr) +{ + this->expr = expr; + if (expr) + expr->setParent(this); +} + +SqliteSelect::Core::JoinConstraint::JoinConstraint(const QList &strList) +{ + columnNames = strList; +} + +SqliteStatement*SqliteSelect::Core::JoinConstraint::clone() +{ + return new SqliteSelect::Core::JoinConstraint(*this); +} + +QStringList SqliteSelect::Core::JoinConstraint::getColumnsInStatement() +{ + QStringList list; + list += columnNames; + return list; +} + +TokenList SqliteSelect::Core::JoinConstraint::getColumnTokensInStatement() +{ + TokenList list; + foreach (TokenPtr token, getTokenListFromNamedKey("inscollist", -1)) + { + if (token->type == Token::OPERATOR) // a COMMA + continue; + + list << token; + } + return list; +} + +SqliteSelect::Core::JoinOp::JoinOp() +{ +} + +SqliteSelect::Core::JoinOp::JoinOp(const SqliteSelect::Core::JoinOp& other) : + SqliteStatement(other), comma(other.comma), joinKw(other.joinKw), naturalKw(other.naturalKw), leftKw(other.leftKw), outerKw(other.outerKw), + innerKw(other.innerKw), crossKw(other.crossKw), rightKw(other.rightKw), fullKw(other.fullKw), customKw1(other.customKw1), + customKw2(other.customKw2), customKw3(other.customKw3) +{ +} + +SqliteSelect::Core::JoinOp::JoinOp(bool comma) +{ + this->comma = comma; + this->joinKw = !comma; +} + +SqliteSelect::Core::JoinOp::JoinOp(const QString &joinToken) +{ + this->joinKw = true; + init(joinToken); +} + +SqliteSelect::Core::JoinOp::JoinOp(const QString &joinToken, const QString &word1) +{ + this->joinKw = true; + init(joinToken); + init(word1); +} + +SqliteSelect::Core::JoinOp::JoinOp(const QString &joinToken, const QString &word1, const QString &word2) +{ + this->joinKw = true; + init(joinToken); + init(word1); + init(word2); +} + +SqliteStatement*SqliteSelect::Core::JoinOp::clone() +{ + return new SqliteSelect::Core::JoinOp(*this); +} + +void SqliteSelect::Core::JoinOp::init(const QString &str) +{ + QString upStr = str.toUpper(); + if (upStr == "NATURAL") + naturalKw = true; + else if (upStr == "LEFT") + leftKw = true; + else if (upStr == "RIGHT") + rightKw = true; + else if (upStr == "FULL") + fullKw = true; + else if (upStr == "OUTER") + outerKw = true; + else if (upStr == "INNER") + innerKw = true; + else if (upStr == "CROSS") + crossKw = true; + else if (customKw1.isNull()) + customKw1 = str; + else if (customKw2.isNull()) + customKw2 = str; + else + customKw3 = str; +} + + +SqliteSelect::Core::JoinSourceOther::JoinSourceOther() +{ +} + +SqliteSelect::Core::JoinSourceOther::JoinSourceOther(const SqliteSelect::Core::JoinSourceOther& other) : + SqliteStatement(other) +{ + DEEP_COPY_FIELD(JoinOp, joinOp); + DEEP_COPY_FIELD(SingleSource, singleSource); + DEEP_COPY_FIELD(JoinConstraint, joinConstraint); +} + +SqliteSelect::Core::JoinSourceOther::JoinSourceOther(SqliteSelect::Core::JoinOp *op, SingleSource *src, JoinConstraint *constr) +{ + this->joinConstraint = constr; + this->joinOp = op; + this->singleSource = src; + if (constr) + constr->setParent(this); + + if (op) + op->setParent(this); + + if (src) + src->setParent(this); +} + +SqliteStatement*SqliteSelect::Core::JoinSourceOther::clone() +{ + return new SqliteSelect::Core::JoinSourceOther(*this); +} + + +SqliteSelect::Core::JoinSource::JoinSource() +{ +} + +SqliteSelect::Core::JoinSource::JoinSource(const JoinSource& other) : + SqliteStatement(other) +{ + DEEP_COPY_FIELD(SingleSource, singleSource); + DEEP_COPY_COLLECTION(JoinSourceOther, otherSources); +} + +SqliteSelect::Core::JoinSource::JoinSource(SqliteSelect::Core::SingleSource *singleSource, const QList &list) +{ + this->singleSource = singleSource; + this->otherSources = list; + if (singleSource) + singleSource->setParent(this); + + foreach (JoinSourceOther* other, otherSources) + other->setParent(this); +} + +SqliteStatement*SqliteSelect::Core::JoinSource::clone() +{ + return new SqliteSelect::Core::JoinSource(*this); +} + + +TokenList SqliteSelect::Core::ResultColumn::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (star) + { + if (!table.isNull()) + builder.withOther(table, dialect).withOperator("."); + + builder.withOperator("*"); + } + else + { + builder.withStatement(expr); + if (!alias.isNull()) + { + if (asKw) + builder.withSpace().withKeyword("AS"); + + builder.withSpace().withOther(alias, dialect); + } + } + + return builder.build(); +} + +TokenList SqliteSelect::Core::SingleSource::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (!table.isNull()) + { + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect); + + if (!alias.isNull()) + { + if (asKw) + builder.withSpace().withKeyword("AS"); + + builder.withSpace().withOther(alias, dialect); + + if (indexedByKw) + builder.withSpace().withKeyword("INDEXED").withSpace().withKeyword("BY").withSpace().withOther(indexedBy, dialect); + else if (notIndexedKw) + builder.withSpace().withKeyword("NOT").withSpace().withKeyword("INDEXED"); + } + } + else if (select) + { + builder.withParLeft().withStatement(select).withParRight(); + if (!alias.isNull()) + { + if (asKw) + builder.withSpace().withKeyword("AS"); + + builder.withSpace().withOther(alias, dialect); + } + } + else + { + builder.withParLeft().withStatement(joinSource).withParRight(); + } + + return builder.build(); +} + +TokenList SqliteSelect::Core::JoinOp::rebuildTokensFromContents() +{ + switch (dialect) + { + case Dialect::Sqlite3: + return rebuildTokensForSqlite2(); + case Dialect::Sqlite2: + return rebuildTokensForSqlite3(); + } + return TokenList(); +} + +TokenList SqliteSelect::Core::JoinOp::rebuildTokensForSqlite2() +{ + StatementTokenBuilder builder; + if (comma) + { + builder.withOperator(","); + } + else + { + if (naturalKw) + builder.withKeyword("NATURAL").withSpace(); + + if (leftKw) + builder.withKeyword("LEFT").withSpace(); + else if (rightKw) + builder.withKeyword("RIGHT").withSpace(); + else if (fullKw) + builder.withKeyword("FULL").withSpace(); + + if (innerKw) + builder.withKeyword("INNER").withSpace(); + else if (crossKw) + builder.withKeyword("CROSS").withSpace(); + else if (outerKw) + builder.withKeyword("OUTER").withSpace(); + + builder.withKeyword("JOIN"); + } + + return builder.build(); +} + +TokenList SqliteSelect::Core::JoinOp::rebuildTokensForSqlite3() +{ + StatementTokenBuilder builder; + if (comma) + { + builder.withOperator(","); + } + else + { + if (naturalKw) + builder.withKeyword("NATURAL").withSpace(); + + if (leftKw) + { + builder.withKeyword("LEFT").withSpace(); + if (outerKw) + builder.withKeyword("OUTER").withSpace(); + } + else if (innerKw) + builder.withKeyword("INNER").withSpace(); + else if (crossKw) + builder.withKeyword("CROSS").withSpace(); + + builder.withKeyword("JOIN"); + } + + return builder.build(); +} + + +TokenList SqliteSelect::Core::JoinConstraint::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (expr) + builder.withKeyword("ON").withStatement(expr); + else + builder.withKeyword("USING").withSpace().withParLeft().withOtherList(columnNames, dialect).withParRight(); + + return builder.build(); +} + + +TokenList SqliteSelect::Core::JoinSourceOther::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withStatement(joinOp).withStatement(singleSource).withStatement(joinConstraint); + return builder.build(); +} + + +TokenList SqliteSelect::Core::JoinSource::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withStatement(singleSource).withStatementList(otherSources, ""); + return builder.build(); +} + + +TokenList SqliteSelect::Core::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (valuesMode) + { + builder.withKeyword("VALUES").withSpace().withParLeft().withStatementList(resultColumns).withParRight(); + return builder.build(); + } + + builder.withKeyword("SELECT"); + if (distinctKw) + builder.withSpace().withKeyword("DISTINCT"); + else if (allKw) + builder.withSpace().withKeyword("ALL"); + + builder.withStatementList(resultColumns); + if (from) + builder.withSpace().withKeyword("FROM").withStatement(from); + + if (where) + builder.withSpace().withKeyword("WHERE").withStatement(where); + + if (groupBy.size() > 0) + { + builder.withSpace().withKeyword("GROUP").withSpace().withKeyword("BY").withStatementList(groupBy); + if (having) + builder.withSpace().withKeyword("HAVING").withStatement(having); + } + + if (orderBy.size() > 0) + builder.withSpace().withKeyword("ORDER").withSpace().withKeyword("BY").withStatementList(orderBy); + + if (limit) + builder.withStatement(limit); + + return builder.build(); +} + +TokenList SqliteSelect::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (with) + builder.withStatement(with); + + foreach (SqliteSelect::Core* core, coreSelects) + { + if (core->compoundOp == CompoundOperator::UNION_ALL) + { + if (core->valuesMode) + builder.withSpace().withOperator(","); + else + builder.withSpace().withKeyword("UNION").withSpace().withKeyword("ALL"); + } + else if (core->compoundOp != CompoundOperator::null) + builder.withSpace().withKeyword(compoundOperator(core->compoundOp)); + + builder.withStatement(core); + } + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.h new file mode 100644 index 0000000..22d1921 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.h @@ -0,0 +1,228 @@ +#ifndef SQLITESELECT_H +#define SQLITESELECT_H + +#include "sqlitequery.h" +#include "sqliteexpr.h" +#include "sqlitelimit.h" +#include "sqliteorderby.h" + +#include + +class SqliteWith; + +/** + * @addtogroup sqlite_statement + * @brief The SqliteSelect class + */ +class API_EXPORT SqliteSelect : public SqliteQuery +{ + public: + enum class CompoundOperator + { + UNION, + UNION_ALL, + INTERSECT, + EXCEPT, + null + }; + + class API_EXPORT Core : public SqliteStatement + { + public: + class API_EXPORT ResultColumn : public SqliteStatement + { + public: + ResultColumn(); + ResultColumn(const ResultColumn& other); + ResultColumn(SqliteExpr* expr, bool asKw, const QString& alias); + ResultColumn(bool star, const QString& table); + explicit ResultColumn(bool star); + + bool isRowId(); + SqliteStatement* clone(); + + SqliteExpr* expr = nullptr; + bool star = false; + bool asKw = false; + QString alias = QString::null; + QString table = QString::null; + + protected: + QStringList getTablesInStatement(); + TokenList getTableTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + }; + + class JoinSource; // forward declaration + + class API_EXPORT SingleSource : public SqliteStatement + { + public: + SingleSource(); + SingleSource(const SingleSource& other); + SingleSource(const QString& name1, const QString& name2, + bool asKw, const QString& alias, bool notIndexedKw, const QString& indexedBy); + SingleSource(SqliteSelect* select, bool asKw, const QString& alias); + SingleSource(JoinSource* src, bool asKw, const QString& alias); + + SqliteStatement* clone(); + + QString database = QString::null; + QString table = QString::null; + QString alias = QString::null; + bool asKw = false; + bool indexedByKw = false; + bool notIndexedKw = false; + QString indexedBy = QString::null; + SqliteSelect* select = nullptr; + JoinSource* joinSource = nullptr; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + }; + + class API_EXPORT JoinOp : public SqliteStatement + { + public: + JoinOp(); + JoinOp(const JoinOp& other); + explicit JoinOp(bool comma); + explicit JoinOp(const QString& joinToken); + JoinOp(const QString& joinToken, const QString& word1); + JoinOp(const QString& joinToken, const QString& word1, const QString& word2); + + SqliteStatement* clone(); + + private: + void init(const QString& str); + + public: + bool comma = false; + bool joinKw = false; + bool naturalKw = false; + bool leftKw = false; + bool outerKw = false; + bool innerKw = false; + bool crossKw = false; + bool rightKw = false; + bool fullKw = false; + QString customKw1 = QString::null; + QString customKw2 = QString::null; + QString customKw3 = QString::null; + + protected: + TokenList rebuildTokensFromContents(); + + private: + TokenList rebuildTokensForSqlite2(); + TokenList rebuildTokensForSqlite3(); + }; + + class API_EXPORT JoinConstraint : public SqliteStatement + { + public: + JoinConstraint(); + JoinConstraint(const JoinConstraint& other); + explicit JoinConstraint(SqliteExpr* expr); + explicit JoinConstraint(const QList& strList); + + SqliteStatement* clone(); + + SqliteExpr* expr = nullptr; + QList columnNames; + + protected: + QStringList getColumnsInStatement(); + TokenList getColumnTokensInStatement(); + TokenList rebuildTokensFromContents(); + }; + + class API_EXPORT JoinSourceOther : public SqliteStatement + { + public: + JoinSourceOther(); + JoinSourceOther(const JoinSourceOther& other); + JoinSourceOther(SqliteSelect::Core::JoinOp *op, + SqliteSelect::Core::SingleSource* src, + SqliteSelect::Core::JoinConstraint* constr); + + SqliteStatement* clone(); + + JoinOp* joinOp = nullptr; + SingleSource* singleSource = nullptr; + JoinConstraint* joinConstraint = nullptr; + + protected: + TokenList rebuildTokensFromContents(); + }; + + class API_EXPORT JoinSource : public SqliteStatement + { + public: + JoinSource(); + JoinSource(const JoinSource& other); + JoinSource(SingleSource* singleSource, const QList& list); + + SqliteStatement* clone(); + + SingleSource* singleSource = nullptr; + QList otherSources; + + protected: + TokenList rebuildTokensFromContents(); + }; + + Core(); + Core(const Core& other); + Core(int distinct, const QList& resCols, JoinSource* src, SqliteExpr* where, + const QList& groupBy, SqliteExpr* having, const QList& orderBy, + SqliteLimit* limit); + + SqliteStatement* clone(); + + CompoundOperator compoundOp = CompoundOperator::null; + QList resultColumns; + JoinSource* from = nullptr; + bool distinctKw = false; + bool allKw = false; + SqliteExpr* where = nullptr; + SqliteExpr* having = nullptr; + QList groupBy; + QList orderBy; + SqliteLimit* limit = nullptr; + bool valuesMode = false; + + protected: + TokenList rebuildTokensFromContents(); + }; + + SqliteSelect(); + SqliteSelect(const SqliteSelect& other); + + static SqliteSelect* append(SqliteSelect::Core* core); + static SqliteSelect* append(SqliteSelect* select, CompoundOperator op, SqliteSelect::Core* core); + static SqliteSelect* append(const QList>& values); + static SqliteSelect* append(SqliteSelect* select, SqliteSelect::CompoundOperator op, const QList>& values); + + SqliteStatement* clone(); + QString compoundOperator(CompoundOperator op); + CompoundOperator compoundOperator(const QString& op); + void reset(); + void setWith(SqliteWith* with); + + QList coreSelects; + SqliteWith* with = nullptr; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteSelectPtr; + +#endif // SQLITESELECT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.cpp new file mode 100644 index 0000000..13f3951 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.cpp @@ -0,0 +1,25 @@ +#include "sqlitesortorder.h" + +SqliteSortOrder sqliteSortOrder(const QString& value) +{ + if (value == "ASC") + return SqliteSortOrder::ASC; + else if (value == "DESC") + return SqliteSortOrder::DESC; + else + return SqliteSortOrder::null; +} + +QString sqliteSortOrder(SqliteSortOrder value) +{ + switch (value) + { + case SqliteSortOrder::ASC: + return "ASC"; + case SqliteSortOrder::DESC: + return "DESC"; + default: + return QString::null; + + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.h new file mode 100644 index 0000000..51c7052 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.h @@ -0,0 +1,17 @@ +#ifndef SQLITESORTORDER_H +#define SQLITESORTORDER_H + +#include "coreSQLiteStudio_global.h" +#include + +enum class SqliteSortOrder +{ + ASC, + DESC, + null +}; + +API_EXPORT SqliteSortOrder sqliteSortOrder(const QString& value); +API_EXPORT QString sqliteSortOrder(SqliteSortOrder value); + +#endif // SQLITESORTORDER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.cpp new file mode 100644 index 0000000..482baf8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.cpp @@ -0,0 +1,553 @@ +#include "sqlitestatement.h" +#include "../token.h" +#include "../lexer.h" +#include "common/unused.h" +#include + +SqliteStatement::SqliteStatement() +{ +} + +SqliteStatement::SqliteStatement(const SqliteStatement& other) : + QObject(), tokens(other.tokens), tokensMap(other.tokensMap), dialect(other.dialect) +{ + +} + +SqliteStatement::~SqliteStatement() +{ +} + +QString SqliteStatement::detokenize() +{ + return tokens.detokenize(); +} + +QStringList SqliteStatement::getContextColumns(bool checkParent, bool checkChilds) +{ + return getContextColumns(this, checkParent, checkChilds); +} + +QStringList SqliteStatement::getContextTables(bool checkParent, bool checkChilds) +{ + return getContextTables(this, checkParent, checkChilds); +} + +QStringList SqliteStatement::getContextDatabases(bool checkParent, bool checkChilds) +{ + return getContextDatabases(this, checkParent, checkChilds); +} + +TokenList SqliteStatement::getContextColumnTokens(bool checkParent, bool checkChilds) +{ + return getContextColumnTokens(this, checkParent, checkChilds); +} + +TokenList SqliteStatement::getContextTableTokens(bool checkParent, bool checkChilds) +{ + return getContextTableTokens(this, checkParent, checkChilds); +} + +TokenList SqliteStatement::getContextDatabaseTokens(bool checkParent, bool checkChilds) +{ + return getContextDatabaseTokens(this, checkParent, checkChilds); +} + +QList SqliteStatement::getContextFullObjects(bool checkParent, bool checkChilds) +{ + QList fullObjects = getContextFullObjects(this, checkParent, checkChilds); + + FullObject fullObj; + QMutableListIterator it(fullObjects); + while (it.hasNext()) + { + fullObj = it.next(); + if (fullObj.type == SqliteStatement::FullObject::NONE) + { + qWarning() << "FullObject of type NONE!"; + it.remove(); + continue; + } + + if (fullObj.type != SqliteStatement::FullObject::DATABASE && !fullObj.object) + { + qWarning() << "No 'object' member in FullObject that is not of DATABASE type!"; + it.remove(); + continue; + } + + if (fullObj.type == SqliteStatement::FullObject::DATABASE && !fullObj.database) + { + qWarning() << "No 'database' member in FullObject that is of DATABASE type!"; + it.remove(); + continue; + } + } + + return fullObjects; +} + +void SqliteStatement::setSqliteDialect(Dialect dialect) +{ + this->dialect = dialect; + foreach (SqliteStatement* stmt, childStatements()) + stmt->setSqliteDialect(dialect); +} + +SqliteStatementPtr SqliteStatement::detach() +{ + if (!parent()) + qWarning() << "Detaching " << this << ", but there's no parent!"; + + setParent(nullptr); + return SqliteStatementPtr(this); +} + +void SqliteStatement::processPostParsing() +{ + evaluatePostParsing(); + foreach (SqliteStatement* stmt, childStatements()) + stmt->processPostParsing(); +} + +QStringList SqliteStatement::getContextColumns(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + QStringList results = getColumnsInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextColumns(this, checkParent, checkChilds); + + return results; +} + +QStringList SqliteStatement::getContextTables(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + QStringList results = getTablesInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextTables(this, checkParent, checkChilds); + + return results; +} + +QStringList SqliteStatement::getContextDatabases(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + QStringList results = getDatabasesInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextDatabases(this, checkParent, checkChilds); + + return results; +} + +TokenList SqliteStatement::getContextColumnTokens(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + TokenList results = getColumnTokensInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextColumnTokens(this, checkParent, checkChilds); + + return results; +} + +TokenList SqliteStatement::getContextTableTokens(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + TokenList results = getTableTokensInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextTableTokens(this, checkParent, checkChilds); + + return results; +} + +TokenList SqliteStatement::getContextDatabaseTokens(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + TokenList results = getDatabaseTokensInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextDatabaseTokens(this, checkParent, checkChilds); + + return results; +} + +QList SqliteStatement::getContextFullObjects(SqliteStatement* caller, bool checkParent, bool checkChilds) +{ + QList results = getFullObjectsInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + { + stmt->setContextDbForFullObject(dbTokenForFullObjects); + results += stmt->getContextFullObjects(this, checkParent, checkChilds); + } + + return results; +} + +QStringList SqliteStatement::getColumnsInStatement() +{ + return QStringList(); +} + +QStringList SqliteStatement::getTablesInStatement() +{ + return QStringList(); +} + +QStringList SqliteStatement::getDatabasesInStatement() +{ + return QStringList(); +} + +TokenList SqliteStatement::getColumnTokensInStatement() +{ + return TokenList(); +} + +TokenList SqliteStatement::getTableTokensInStatement() +{ + return TokenList(); +} + +TokenList SqliteStatement::getDatabaseTokensInStatement() +{ + return TokenList(); +} + +QList SqliteStatement::getFullObjectsInStatement() +{ + return QList(); +} + +TokenList SqliteStatement::rebuildTokensFromContents() +{ + qCritical() << "called rebuildTokensFromContents() for SqliteStatement that has no implementation for it."; + return TokenList(); +} + +void SqliteStatement::evaluatePostParsing() +{ +} + +QList SqliteStatement::getContextStatements(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + QList results; + + SqliteStatement* stmt = parentStatement(); + if (checkParent && stmt && stmt != caller) + results += stmt; + + if (checkChilds) + { + foreach (stmt, childStatements()) + { + if (stmt == caller) + continue; + + results += stmt; + } + } + + return results; +} + +TokenList SqliteStatement::extractPrintableTokens(const TokenList &tokens, bool skipMeaningless) +{ + TokenList list; + foreach (TokenPtr token, tokens) + { + switch (token->type) + { + case Token::OTHER: + case Token::STRING: + case Token::FLOAT: + case Token::INTEGER: + case Token::BIND_PARAM: + case Token::OPERATOR: + case Token::PAR_LEFT: + case Token::PAR_RIGHT: + case Token::BLOB: + case Token::KEYWORD: + list << token; + break; + case Token::COMMENT: + case Token::SPACE: + if (!skipMeaningless) + list << token; + break; + default: + break; + } + } + return list; +} + +QStringList SqliteStatement::getStrListFromValue(const QString &value) +{ + QStringList list; + if (!value.isNull()) + list << value; + + return list; +} + +TokenList SqliteStatement::getTokenListFromNamedKey(const QString &tokensMapKey, int idx) +{ + TokenList list; + if (tokensMap.contains(tokensMapKey)) + { + if (idx < 0) + list += extractPrintableTokens(tokensMap[tokensMapKey]); + else if (tokensMap[tokensMapKey].size() > idx) + list << extractPrintableTokens(tokensMap[tokensMapKey])[idx]; + } + else + qCritical() << "No '" << tokensMapKey << "' in tokens map when asked for it in getTokenListFromNamedKey()."; + + return list; +} + +TokenPtr SqliteStatement::getDbTokenFromFullname(const QString &tokensMapKey) +{ + if (!tokensMap.contains(tokensMapKey)) + { + qCritical() << "No '" << tokensMapKey << "' in tokens map when asked for it getDbTokenFromFullname()."; + return TokenPtr(); + } + + TokenList tokens = extractPrintableTokens(tokensMap[tokensMapKey]); + + if (tokens.size() == 3) + return tokens[0]; + else if (tokens.size() == 1) + return TokenPtr(); + else + qCritical() << "Expected 1 or 3 tokens in '" << tokensMapKey << "' in tokens map, but got" << tokens.size(); + + return TokenPtr(); +} + +TokenPtr SqliteStatement::getObjectTokenFromFullname(const QString &tokensMapKey) +{ + if (!tokensMap.contains(tokensMapKey)) + { + qCritical() << "No '" << tokensMapKey << "' in tokens map when asked for it."; + return TokenPtr(); + } + + TokenList tokens = extractPrintableTokens(tokensMap[tokensMapKey]); + if (tokens.size() == 3) + return tokens[2]; + else if (tokens.size() == 1) + return tokens[0]; + else + qCritical() << "Expected 1 or 3 tokens in '" << tokensMapKey << "' in tokens map, but got" << tokens.size(); + + return TokenPtr(); +} + +TokenPtr SqliteStatement::getDbTokenFromNmDbnm(const QString &tokensMapKey1, const QString &tokensMapKey2) +{ + if (!tokensMap.contains(tokensMapKey1)) + { + qCritical() << "No '" << tokensMapKey1 << "' in tokens map when asked for it in getDbTokenFromNmDbnm()."; + return TokenPtr(); + } + + // It seems like checking tokensMapKey2 has no added value to the logic, while it prevents from reporting + // database token in case of: SELECT * FROM dbName. <- the valid query with "minor" error + UNUSED(tokensMapKey2); +// if (!tokensMap.contains(tokensMapKey2)) +// { +// qCritical() << "No '" << tokensMapKey2 << "' in tokens map when asked for it in getDbTokenFromNmDbnm()."; +// return TokenPtr(); +// } + +// if (tokensMap[tokensMapKey2].size() == 0) +// return TokenPtr(); + + if (!tokensMap.contains("DOT") && tokensMap[tokensMapKey2].size() == 0) + { + // In this case the query is "SELECT * FROM test" and there is no database, + // but if there was a dot after the "test", then the "test" is a database name, + // so this block won't be executed. Instead the name of the database will be returned below. + return TokenPtr(); + } + + return extractPrintableTokens(tokensMap[tokensMapKey1])[0]; +} + +TokenPtr SqliteStatement::getObjectTokenFromNmDbnm(const QString &tokensMapKey1, const QString &tokensMapKey2) +{ + if (!tokensMap.contains(tokensMapKey1)) + { + qCritical() << "No '" << tokensMapKey1 << "' in tokens map when asked for it in getObjectTokenFromNmDbnm()."; + return TokenPtr(); + } + + if (!tokensMap.contains(tokensMapKey2)) + { + qCritical() << "No '" << tokensMapKey2 << "' in tokens map when asked for it in getObjectTokenFromNmDbnm()."; + return TokenPtr(); + } + + if (tokensMap[tokensMapKey2].size() == 0) + return extractPrintableTokens(tokensMap[tokensMapKey1])[0]; + + return extractPrintableTokens(tokensMap[tokensMapKey2])[1]; +} + +TokenList SqliteStatement::getDbTokenListFromFullname(const QString &tokensMapKey) +{ + TokenList list; + TokenPtr token = getDbTokenFromFullname(tokensMapKey); + if (token) + list << token; + + return list; +} + +TokenList SqliteStatement::getObjectTokenListFromFullname(const QString &tokensMapKey) +{ + TokenList list; + TokenPtr token = getObjectTokenFromFullname(tokensMapKey); + if (token) + list << token; + + return list; +} + +TokenList SqliteStatement::getDbTokenListFromNmDbnm(const QString &tokensMapKey1, const QString &tokensMapKey2) +{ + TokenList list; + TokenPtr token = getDbTokenFromNmDbnm(tokensMapKey1, tokensMapKey2); + if (token) + list << token; + + return list; +} + +TokenList SqliteStatement::getObjectTokenListFromNmDbnm(const QString &tokensMapKey1, const QString &tokensMapKey2) +{ + TokenList list; + TokenPtr token = getObjectTokenFromNmDbnm(tokensMapKey1, tokensMapKey2); + if (token) + list << token; + + return list; +} + +SqliteStatement::FullObject SqliteStatement::getFullObjectFromFullname(SqliteStatement::FullObject::Type type, const QString& tokensMapKey) +{ + return getFullObject(type, getDbTokenFromFullname(tokensMapKey), getObjectTokenFromFullname(tokensMapKey)); +} + +SqliteStatement::FullObject SqliteStatement::getFullObjectFromNmDbnm(SqliteStatement::FullObject::Type type, const QString& tokensMapKey1, const QString& tokensMapKey2) +{ + return getFullObject(type, getDbTokenFromNmDbnm(tokensMapKey1, tokensMapKey2), getObjectTokenFromNmDbnm(tokensMapKey1, tokensMapKey2)); +} + +SqliteStatement::FullObject SqliteStatement::getFullObject(SqliteStatement::FullObject::Type type, TokenPtr dbToken, TokenPtr objToken) +{ + FullObject fullObj; + if (!objToken) + return fullObj; + + fullObj.database = dbToken; + fullObj.object = objToken; + fullObj.type = type; + return fullObj; +} + +void SqliteStatement::setContextDbForFullObject(TokenPtr dbToken) +{ + dbTokenForFullObjects = dbToken; +} + +SqliteStatement::FullObject SqliteStatement::getFirstDbFullObject() +{ + TokenList dbTokens = getDatabaseTokensInStatement(); + return getDbFullObject(dbTokens.size() > 0 ? dbTokens[0] : TokenPtr()); +} + +SqliteStatement::FullObject SqliteStatement::getDbFullObject(TokenPtr dbToken) +{ + FullObject fullObj; + if (!dbToken) + return fullObj; + + fullObj.database = dbToken; + fullObj.type = FullObject::DATABASE; + return fullObj; +} + +Range SqliteStatement::getRange() +{ + if (tokens.size() == 0) + return Range(0, 0); + + return Range(tokens.first()->start, tokens.last()->end); +} + +SqliteStatement *SqliteStatement::findStatementWithToken(TokenPtr token) +{ + SqliteStatement* stmtWithToken = nullptr; + foreach (SqliteStatement* stmt, childStatements()) + { + stmtWithToken = stmt->findStatementWithToken(token); + if (stmtWithToken) + return stmtWithToken; + } + + if (tokens.contains(token)) + return this; + + return nullptr; +} + +SqliteStatement *SqliteStatement::findStatementWithPosition(quint64 cursorPosition) +{ + TokenPtr token = tokens.atCursorPosition(cursorPosition); + if (!token) + return nullptr; + + return findStatementWithToken(token); +} + +SqliteStatement *SqliteStatement::parentStatement() +{ + if (!parent()) + return nullptr; + + return dynamic_cast(parent()); +} + +QList SqliteStatement::childStatements() +{ + QList results; + foreach (QObject* obj, children()) + results += dynamic_cast(obj); + + return results; +} + +void SqliteStatement::rebuildTokens() +{ + tokens.clear(); + tokensMap.clear(); + tokens = rebuildTokensFromContents(); + // TODO rebuild tokensMap as well + // It shouldn't be hard to write unit tests that parse a query, remembers it tokensMap, then rebuilds tokens from contents + // and then compare new tokens map with previous one. This way we should be able to get all maps correctly. +} + +void SqliteStatement::setParent(QObject* parent) +{ + QObject::setParent(parent); + SqliteStatement* stmt = qobject_cast(parent); + if (stmt) + dialect = stmt->dialect; +} + +void SqliteStatement::attach(SqliteStatement*& memberForChild, SqliteStatement* childStatementToAttach) +{ + memberForChild = childStatementToAttach; + childStatementToAttach->setParent(this); +} + +bool SqliteStatement::FullObject::isValid() const +{ + return (object != nullptr || (type == DATABASE && database != nullptr)); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h new file mode 100644 index 0000000..bacb2d7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h @@ -0,0 +1,339 @@ +#ifndef SQLITESTATEMENT_H +#define SQLITESTATEMENT_H + +#include "common/utils.h" +#include "parser/token.h" +#include "dialect.h" +#include +#include +#include +#include +#include +#include + +// TODO start using attach() in most cases where setParent() is used + +class SqliteStatement; +typedef QSharedPointer SqliteStatementPtr; + +/** + * @defgroup sqlite_statement Parser result containers + */ + +/** + * @ingroup sqlite_statement + * + * @brief General container for any type of parsed object. + * + * Statements can have multiple children statements and single parent statement. + * This way they create a tree of objects. Since those statements represent various language + * statements (here SQL), such tree is also called an Abstract Syntax Tree (aka AST). + * + * The AST lets you to examine structure of the query in a logical manner. + * In other words, once you parse query into AST, you have a tree of object, where each oject in + * the tree represents some part of the query and provides all its parameters as member variables. + * + * Deleting single statement causes all it's children to be deleted automatically. + * + * @section output_from_parser SqliteStatement as output from Parser + * + * The SqliteStatement is the most generic representation of Parser processing results. + * Every parsed query is represented by its specialized, derived class, but it's also + * the SqliteQuery and every SqliteQuery is also the SqliteStatement. + * + * Apart from SqliteQuery objects, which represent complete SQLite queries, there are also + * other statements, like expressions (http://sqlite.org/lang_expr.html) and others. + * Those statements don't inherit from SqliteQuery, but directly from SqliteStatement. + * + * Every parsed statement contains list of tokens that were used to parse this statement + * in SqliteStatement::tokens. + * + * There is also SqliteStatement::tokensMap, which is a table mapping grammar rule name + * into tokens used to fulfill that rule. To learn possible keys for each SqliteStatement, + * you have to look into sqlite2.y and sqlite3.y files and see definition of the statement, + * that you're examining SqliteStatement::tokensMap for. + * + * @note SqliteStatement::tokensMap is a low level API and it's not very predictible, + * unless you get to know it very well. That's why it's not recommended to use it. + * + * Example of working with SqliteStatement::tokensMap: you have a SqliteAttachPtr from the parser. + * You can learn the "attach name" from SqliteAttach::name, like this: + * @code + * QString name; + * if (attachPtr->name) + * name = attachPtr->name->detokenize(); + * @endcode + * or you can use tokensMap like this: + * @code + * QString name; + * if (attachPtr->tokensMap["expr2"]) + * name = attachPtr->tokensMap["expr2"]->detokenize(); + * @endcode + * + * Why using tokensMap, when you can read values from object member easly? Well, object members + * have plain values (string, integer, etc), while tokensMap has tokens, so you can examine + * at which character exactly was the value placed, where it ended, etc. + * + * @section query_generation SqliteStatement as utility for generating query string + * + * Generating query string with SqliteStatement makes sense only in case, when you have parsed + * query and got SqliteStatement as a result. You can modify some parameters of the query + * and detokenize it back to SQL string. This is done in 4 steps: + *
    + *
  • Parse SQL query string,
  • + *
  • Modify values in parsed statements,
  • + *
  • Re-generate tokens in all modified statements,
  • + *
  • Detokenize tokens from statements.
  • + *
+ * + * This is how it's usually done: + * @code + * // STEP 1 + * Parser parser(db->getDialect()); + * if (!parser.parse("SELECT column FROM test WHERE value = 5") || parser.getQueries().size() == 0) + * { + * // handle parsing error, or no queries parsed (which is also some kind of error) + * return; + * } + * + * SqliteQueryPtr query = parser.getQueries().first(); + * SqliteSelectPtr select = query.dynamicCast(); + * if (!select) + * { + * // we want to deal with the SELECT only + * return; + * } + * + * // STEP 2 + * SqliteSelect::Core* core = select->coreSelects.first(); + * + * // Prepare new result column statement + * SqliteSelect::Core::ResultColumn* resCol = new SqliteSelect::Core::ResultColumn(); + * + * SqliteExpr* expr = new SqliteExpr(); // create expression for result column + * expr->initLiteral("test value"); // let the expression be a constant literal value + * + * resCol->attach(resCol->expr, expr); // put the defined expression into result column statement + * core->attach(core->resultColumns, resCol); // add new result column to rest of columns + * + * // STEP 3 + * select->rebuildTokens(); + * + * // STEP 4 + * QString newQuery = select->detokenize(); + * @endcode + * + * In the result, the newQuery will contain: SELECT column, 'test value' FROM test WHERE value = 5. + * + * @warning It is important to use SqliteStatement::attach() and SqliteStatement::detach() + * when modifying AST layout. The tree hierarchy is used to delete objects recurrently, + * so deleting the SqliteSelect will also delete all it's children. Assembling or disassembling statements + * manually (without SqliteStatement::attach() and SqliteStatement::detach()) is not safe and will most likely + * result in a memory leak, or application crash. + * + * For example of SqliteStatement::detach() usage see section below. + * + * @section ptr_vs_shared_ptr C++ pointer to SqliteStatement vs. SqliteStatementPtr + * + * SqliteStatementPtr is a shared pointer (QSharedPointer) to SqliteStatement. All derived classes + * also have variations of their types as shared pointers. However only the top level objects + * returned from the Parser are actually provided as shared pointers. All children objects are + * regular C++ pointers. The reason behind this is to avoid memory leaks. Top level objects from Parser + * are shared pointers, so you can use them casually, without worrying who and when should delete them. + * On the other hand, any children of those top level objects are regular pointers, cause they will + * be deleted automatically when their parent is deleted. + * + * Sometimes you might want to use just some child statement from parsed query. Normally you would need to + * assign that child statement to some local variable and reset its parent to nullptr. Fortunately + * SqliteStatement provides handful method SqliteStatement::detach(), which does all that for you. + * It also provides detached statement as a new shared pointer, so it's easier to manage it. + * Additionally there's a template version of detach() method which can return detached statement + * as provided statement type. + * + * Example: + * @code + * Parser parser(db->getDialect()); + * if (!parser.parse("SELECT column FROM test WHERE value = 5") || parser.getQueries().size() == 0) + * { + * // handle parsing error, or no queries parsed (which is also some kind of error) + * return SqliteExprPtr(); + * } + * + * SqliteQueryPtr query = parser.getQueries().first(); + * SqliteSelectPtr select = query.dynamicCast(); + * if (!select) + * { + * // we want to deal with the SELECT only + * return SqliteExprPtr(); + * } + * + * SqliteSelect::Core* core = select->coreSelects.first(); + * + * // Our point is to get the SqliteExpr which represents the result column "column". + * SqliteExprPtr expr = core->resultColumns.first().detach(); + * return expr; + * @endcode + * + * After the above parser goes out of scope, so it's deleted and all its parsed + * queries get deleted as well, because their shared pointers were not copied anywhere else. + * The captured expr would normally also be deleted, but when we detached it, it became + * an independed entity, with its own lifetime. + * + * For the opposite operation, use SqliteStatement::attach(). + */ +class API_EXPORT SqliteStatement : public QObject +{ + Q_OBJECT + + public: + struct FullObject + { + enum Type + { + TABLE, + INDEX, + TRIGGER, + VIEW, + DATABASE, + NONE + }; + + bool isValid() const; + + Type type = NONE; + TokenPtr database; + TokenPtr object; + }; + + SqliteStatement(); + SqliteStatement(const SqliteStatement& other); + virtual ~SqliteStatement(); + + QString detokenize(); + Range getRange(); + SqliteStatement* findStatementWithToken(TokenPtr token); + SqliteStatement* findStatementWithPosition(quint64 cursorPosition); + SqliteStatement* parentStatement(); + QList childStatements(); + QStringList getContextColumns(bool checkParent = true, bool checkChilds = true); + QStringList getContextTables(bool checkParent = true, bool checkChilds = true); + QStringList getContextDatabases(bool checkParent = true, bool checkChilds = true); + TokenList getContextColumnTokens(bool checkParent = true, bool checkChilds = true); + TokenList getContextTableTokens(bool checkParent = true, bool checkChilds = true); + TokenList getContextDatabaseTokens(bool checkParent = true, bool checkChilds = true); + QList getContextFullObjects(bool checkParent = true, bool checkChilds = true); + void setSqliteDialect(Dialect dialect); + void rebuildTokens(); + void setParent(QObject* parent); + void attach(SqliteStatement*& memberForChild, SqliteStatement* childStatementToAttach); + SqliteStatementPtr detach(); + void processPostParsing(); + virtual SqliteStatement* clone() = 0; + + template + void attach(QList& listMemberForChild, T* childStatementToAttach) + { + listMemberForChild << childStatementToAttach; + childStatementToAttach->setParent(this); + } + + template + QSharedPointer detach() {return detach().dynamicCast();} + + template + QList getAllTypedStatements() + { + QList results; + + T* casted = dynamic_cast(this); + if (casted) + results << casted; + + foreach (SqliteStatement* stmt, getContextStatements(this, false, true)) + results += stmt->getAllTypedStatements(); + + return results; + } + + /** + * @brief Ordered list of all tokens for this statement. + * An ordered list of tokens that represent current statement. Tokens include + * Token::SPACE and Token::COMMENT types, so detokenizing this list results + * in the equal SQL string as was passed to the parser at the begining. + */ + TokenList tokens; + + /** + * @brief Map of grammar terminals and non-terminals into their tokens. + * This is map of ordered token lists that represent each node of SQLite grammar definition + * used to build this statement. For example grammar definition: + * test ::= value1 TERMINAL_TOKEN value2 + * will result in tokens map containing following entries: + * value1 = {list of tokens from value1} + * TERMINAL_TOKEN = {list of only one token: TERMINAL_TOKEN} + * value2 = {list of tokens from value2} + * + * In case there are two non-terminals with same name used (for example "name name name"), + * then first non-terminal is used as a key just as is, but second is renamed to "name2", + * and third to "name3". If we had example: + * test ::= value TERMINAL_TOKEN value + * then it will result in tokens map containing following entries: + * value = {list of tokens from first value} + * TERMINAL_TOKEN = {list of only one token: TERMINAL_TOKEN} + * value2 = {list of tokens from second value} + */ + QHash tokensMap; + + Dialect dialect = Dialect::Sqlite3; + + protected: + QStringList getContextColumns(SqliteStatement* caller, bool checkParent, bool checkChilds); + QStringList getContextTables(SqliteStatement* caller, bool checkParent, bool checkChilds); + QStringList getContextDatabases(SqliteStatement* caller, bool checkParent, bool checkChilds); + TokenList getContextColumnTokens(SqliteStatement* caller, bool checkParent, bool checkChilds); + TokenList getContextTableTokens(SqliteStatement* caller, bool checkParent, bool checkChilds); + TokenList getContextDatabaseTokens(SqliteStatement* caller, bool checkParent, bool checkChilds); + QList getContextFullObjects(SqliteStatement* caller, bool checkParent, bool checkChilds); + + virtual QStringList getColumnsInStatement(); + virtual QStringList getTablesInStatement(); + virtual QStringList getDatabasesInStatement(); + virtual TokenList getColumnTokensInStatement(); + virtual TokenList getTableTokensInStatement(); + virtual TokenList getDatabaseTokensInStatement(); + virtual QList getFullObjectsInStatement(); + virtual TokenList rebuildTokensFromContents(); + virtual void evaluatePostParsing(); + + static TokenList extractPrintableTokens(const TokenList& tokens, bool skipMeaningless = true); + QStringList getStrListFromValue(const QString& value); + TokenList getTokenListFromNamedKey(const QString& tokensMapKey, int idx = 0); + TokenPtr getDbTokenFromFullname(const QString& tokensMapKey = "fullname"); + TokenPtr getObjectTokenFromFullname(const QString& tokensMapKey = "fullname"); + TokenPtr getDbTokenFromNmDbnm(const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); + TokenPtr getObjectTokenFromNmDbnm(const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); + TokenList getDbTokenListFromFullname(const QString& tokensMapKey = "fullname"); + TokenList getObjectTokenListFromFullname(const QString& tokensMapKey = "fullname"); + TokenList getDbTokenListFromNmDbnm(const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); + TokenList getObjectTokenListFromNmDbnm(const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); + FullObject getFullObjectFromFullname(FullObject::Type type, const QString& tokensMapKey = "fullname"); + FullObject getFullObjectFromNmDbnm(FullObject::Type type, const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); + FullObject getFirstDbFullObject(); + FullObject getDbFullObject(TokenPtr dbToken); + FullObject getFullObject(SqliteStatement::FullObject::Type type, TokenPtr dbToken, TokenPtr objToken); + void setContextDbForFullObject(TokenPtr dbToken); + + /** + * @brief Token representing a database. + * Keeps db context for getFullObjectsInStatement(), so for example "CREATE TABLE xyz.abc (id)" will know, + * that for column "id" the database is "xyz" and table "abc". The value is spread across childrens + * of this statement when getContextFullObjects() is called. + * The value of this variable is defined in overwritten implementation of getFullObjectsInStatement() method. + */ + TokenPtr dbTokenForFullObjects; + + private: + QList getContextStatements(SqliteStatement* caller, bool checkParent, bool checkChilds); +}; + +#endif // SQLITESTATEMENT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitetablerelatedddl.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitetablerelatedddl.h new file mode 100644 index 0000000..599849a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitetablerelatedddl.h @@ -0,0 +1,14 @@ +#ifndef SQLITETABLERELATEDDDL_H +#define SQLITETABLERELATEDDDL_H + +#include "coreSQLiteStudio_global.h" + +class API_EXPORT SqliteTableRelatedDdl +{ + public: + virtual QString getTargetTable() const = 0; +}; + +typedef QSharedPointer SqliteTableRelatedDdlPtr; + +#endif // SQLITETABLERELATEDDDL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.cpp new file mode 100644 index 0000000..88ac28b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.cpp @@ -0,0 +1,207 @@ +#include "sqliteupdate.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include "sqlitewith.h" +#include + +SqliteUpdate::SqliteUpdate() +{ + queryType = SqliteQueryType::Update; +} + +SqliteUpdate::SqliteUpdate(const SqliteUpdate& other) : + SqliteQuery(other), onConflict(other.onConflict), database(other.database), table(other.table), indexedByKw(other.indexedByKw), + notIndexedKw(other.notIndexedKw), indexedBy(other.indexedBy) +{ + // Special case of deep collection copy + SqliteExpr* newExpr = nullptr; + foreach (const ColumnAndValue& keyValue, other.keyValueMap) + { + newExpr = new SqliteExpr(*keyValue.second); + newExpr->setParent(this); + keyValueMap << ColumnAndValue(keyValue.first, newExpr); + } + + DEEP_COPY_FIELD(SqliteExpr, where); + DEEP_COPY_FIELD(SqliteWith, with); +} + +SqliteUpdate::~SqliteUpdate() +{ +} + +SqliteUpdate::SqliteUpdate(SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, bool notIndexedKw, const QString &indexedBy, + const QList > values, SqliteExpr *where, SqliteWith* with) + : SqliteUpdate() +{ + this->onConflict = onConflict; + + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; + + this->indexedBy = indexedBy; + this->indexedByKw = !(indexedBy.isNull()); + this->notIndexedKw = notIndexedKw; + keyValueMap = values; + + this->where = where; + if (where) + where->setParent(this); + + this->with = with; + if (with) + with->setParent(this); + + foreach (const ColumnAndValue& keyValue, keyValueMap) + keyValue.second->setParent(this); +} + +SqliteStatement*SqliteUpdate::clone() +{ + return new SqliteUpdate(*this); +} + +SqliteExpr* SqliteUpdate::getValueForColumnSet(const QString& column) +{ + foreach (const ColumnAndValue& keyValue, keyValueMap) + { + if (keyValue.first == column) + return keyValue.second; + } + return nullptr; +} + +QStringList SqliteUpdate::getColumnsInStatement() +{ + QStringList columns; + foreach (const ColumnAndValue& keyValue, keyValueMap) + columns += keyValue.first; + + return columns; +} + +QStringList SqliteUpdate::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteUpdate::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteUpdate::getColumnTokensInStatement() +{ + // This case is not simple. We only have "setlist" in tokensMap + // and it contains entire: col = expr, col = expr. + // In order to extract 'col' token, we go through all 'expr', + // for each 'expr' we get its first token, then locate it + // in entire "setlist", get back 2 tokens to get what's before "=". + TokenList list; + TokenList setListTokens = getTokenListFromNamedKey("setlist"); + int setListTokensSize = setListTokens.size(); + int colNameTokenIdx; + SqliteExpr* expr = nullptr; + foreach (const ColumnAndValue& keyValue, keyValueMap) + { + expr = keyValue.second; + colNameTokenIdx = setListTokens.indexOf(expr->tokens[0]) - 2; + if (colNameTokenIdx < 0 || colNameTokenIdx > setListTokensSize) + { + qCritical() << "Went out of bounds while looking for column tokens in SqliteUpdate::getColumnTokensInStatement()."; + continue; + } + list << setListTokens[colNameTokenIdx]; + } + return list; +} + +TokenList SqliteUpdate::getTableTokensInStatement() +{ + if (tokensMap.contains("fullname")) + return getObjectTokenListFromFullname(); + + return TokenList(); +} + +TokenList SqliteUpdate::getDatabaseTokensInStatement() +{ + if (tokensMap.contains("fullname")) + return getDbTokenListFromFullname(); + + if (tokensMap.contains("nm")) + return extractPrintableTokens(tokensMap["nm"]); + + return TokenList(); +} + +QList SqliteUpdate::getFullObjectsInStatement() +{ + QList result; + if (!tokensMap.contains("fullname")) + return result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +TokenList SqliteUpdate::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (with) + builder.withStatement(with); + + builder.withKeyword("UPDATE").withSpace(); + if (onConflict != SqliteConflictAlgo::null) + builder.withKeyword("OR").withSpace().withKeyword(sqliteConflictAlgo(onConflict)).withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect).withSpace(); + + if (indexedByKw) + builder.withKeyword("INDEXED").withSpace().withKeyword("BY").withSpace().withOther(indexedBy, dialect).withSpace(); + else if (notIndexedKw) + builder.withKeyword("NOT").withSpace().withKeyword("INDEXED").withSpace(); + + builder.withKeyword("SET").withSpace(); + + bool first = true; + foreach (const ColumnAndValue& keyVal, keyValueMap) + { + if (!first) + builder.withOperator(",").withSpace(); + + builder.withOther(keyVal.first, dialect).withSpace().withOperator("=").withStatement(keyVal.second); + first = false; + } + + if (where) + builder.withSpace().withKeyword("WHERE").withStatement(where); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.h new file mode 100644 index 0000000..7d6e0c1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.h @@ -0,0 +1,51 @@ +#ifndef SQLITEUPDATE_H +#define SQLITEUPDATE_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" + +#include +#include + +class SqliteExpr; +class SqliteWith; + +class API_EXPORT SqliteUpdate : public SqliteQuery +{ + public: + typedef QPair ColumnAndValue; + + SqliteUpdate(); + SqliteUpdate(const SqliteUpdate& other); + ~SqliteUpdate(); + SqliteUpdate(SqliteConflictAlgo onConflict, const QString& name1, const QString& name2, + bool notIndexedKw, const QString& indexedBy, const QList > values, + SqliteExpr* where, SqliteWith* with); + + SqliteStatement* clone(); + SqliteExpr* getValueForColumnSet(const QString& column); + + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + QString database = QString::null; + QString table = QString::null; + bool indexedByKw = false; + bool notIndexedKw = false; + QString indexedBy = QString::null; + QList keyValueMap; + SqliteExpr* where = nullptr; + SqliteWith* with = nullptr; + + protected: + QStringList getColumnsInStatement(); + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getColumnTokensInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteUpdatePtr; + +#endif // SQLITEUPDATE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.cpp new file mode 100644 index 0000000..ab7d00e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.cpp @@ -0,0 +1,56 @@ +#include "sqlitevacuum.h" +#include "sqlitequerytype.h" + +#include + +SqliteVacuum::SqliteVacuum() +{ + queryType = SqliteQueryType::Vacuum; +} + +SqliteVacuum::SqliteVacuum(const SqliteVacuum& other) : + SqliteQuery(other), database(other.database) +{ +} + +SqliteVacuum::SqliteVacuum(const QString& name) + : SqliteVacuum() +{ + if (!name.isNull()) + database = name; +} + +SqliteStatement*SqliteVacuum::clone() +{ + return new SqliteVacuum(*this); +} + +QStringList SqliteVacuum::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteVacuum::getDatabaseTokensInStatement() +{ + return getTokenListFromNamedKey("nm"); +} + +QList SqliteVacuum::getFullObjectsInStatement() +{ + QList result; + + // Db object + FullObject fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteVacuum::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("VACUUM").withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.h new file mode 100644 index 0000000..871b8f4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.h @@ -0,0 +1,28 @@ +#ifndef SQLITEVACUUM_H +#define SQLITEVACUUM_H + +#include "sqlitequery.h" + +#include + +class API_EXPORT SqliteVacuum : public SqliteQuery +{ + public: + SqliteVacuum(); + SqliteVacuum(const SqliteVacuum& other); + explicit SqliteVacuum(const QString &name); + + SqliteStatement* clone(); + + QString database; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteVacuumPtr; + +#endif // SQLITEVACUUM_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.cpp new file mode 100644 index 0000000..2b9c99f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.cpp @@ -0,0 +1,87 @@ +#include "sqlitewith.h" +#include "parser/statementtokenbuilder.h" +#include "sqliteselect.h" +#include "common/global.h" + +SqliteWith::SqliteWith() +{ +} + +SqliteWith::SqliteWith(const SqliteWith& other) : + SqliteStatement(other), recursive(other.recursive) +{ + DEEP_COPY_COLLECTION(CommonTableExpression, cteList); +} + +SqliteWith* SqliteWith::append(const QString& tableName, const QList& indexedColumns, SqliteSelect* select) +{ + SqliteWith* with = new SqliteWith(); + CommonTableExpression* cte = new CommonTableExpression(tableName, indexedColumns, select); + cte->setParent(with); + with->cteList << cte; + return with; +} + +SqliteWith* SqliteWith::append(SqliteWith* with, const QString& tableName, const QList& indexedColumns, SqliteSelect* select) +{ + if (!with) + with = new SqliteWith(); + + CommonTableExpression* cte = new CommonTableExpression(tableName, indexedColumns, select); + cte->setParent(with); + with->cteList << cte; + return with; +} + +SqliteStatement*SqliteWith::clone() +{ + return new SqliteWith(*this); +} + +TokenList SqliteWith::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("WITH").withSpace(); + if (recursive) + builder.withKeyword("RECURSIVE").withSpace(); + + builder.withStatementList(cteList); + + return builder.build(); +} + +SqliteWith::CommonTableExpression::CommonTableExpression() +{ +} + +SqliteWith::CommonTableExpression::CommonTableExpression(const SqliteWith::CommonTableExpression& other) : + SqliteStatement(other), table(other.table) +{ + DEEP_COPY_COLLECTION(SqliteIndexedColumn, indexedColumns); + DEEP_COPY_FIELD(SqliteSelect, select); +} + +SqliteWith::CommonTableExpression::CommonTableExpression(const QString& tableName, const QList& indexedColumns, SqliteSelect* select) : + table(tableName), indexedColumns(indexedColumns), select(select) +{ + select->setParent(this); +} + +SqliteStatement*SqliteWith::CommonTableExpression::clone() +{ + return new SqliteWith::CommonTableExpression(*this); +} + +TokenList SqliteWith::CommonTableExpression::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withOther(table, dialect); + + if (indexedColumns.size() > 0) + builder.withSpace().withParLeft().withStatementList(indexedColumns).withParRight(); + + builder.withSpace().withKeyword("AS").withSpace().withParLeft().withStatement(select).withParRight(); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.h new file mode 100644 index 0000000..fe64c9c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.h @@ -0,0 +1,45 @@ +#ifndef SQLITEWITH_H +#define SQLITEWITH_H + +#include "sqlitestatement.h" +#include "sqliteindexedcolumn.h" + +class SqliteSelect; + +class SqliteWith : public SqliteStatement +{ + public: + class CommonTableExpression : public SqliteStatement + { + public: + CommonTableExpression(); + CommonTableExpression(const CommonTableExpression& other); + CommonTableExpression(const QString& tableName, const QList& indexedColumns, SqliteSelect* select); + + SqliteStatement* clone(); + + QString table; + QList indexedColumns; + SqliteSelect* select = nullptr; + + protected: + TokenList rebuildTokensFromContents(); + }; + + SqliteWith(); + SqliteWith(const SqliteWith& other); + static SqliteWith* append(const QString& tableName, const QList& indexedColumns, SqliteSelect* select); + static SqliteWith* append(SqliteWith* with, const QString& tableName, const QList& indexedColumns, SqliteSelect* select); + + SqliteStatement* clone(); + + QList cteList; + bool recursive = false; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer SqliteWithPtr; + +#endif // SQLITEWITH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp new file mode 100644 index 0000000..2088ff2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp @@ -0,0 +1,324 @@ +#include "keywords.h" +#include "sqlite3_parse.h" +#include "sqlite2_parse.h" +#include +#include + +QHash keywords2; +QHash keywords3; +QSet rowIdKeywords; +QStringList joinKeywords; +QStringList fkMatchKeywords; +QStringList conflictAlgoKeywords; + +int getKeywordId2(const QString& str) +{ + QString upStr = str.toUpper(); + if (keywords2.contains(upStr)) + return keywords2[upStr]; + else + return TK2_ID; +} + +int getKeywordId3(const QString& str) +{ + QString upStr = str.toUpper(); + if (keywords3.contains(upStr)) + return keywords3[upStr]; + else + return TK3_ID; +} + +bool isRowIdKeyword(const QString& str) +{ + return rowIdKeywords.contains(str.toUpper()); +} + +const QHash& getKeywords2() +{ + return keywords2; +} + +const QHash& getKeywords3() +{ + return keywords3; +} + +void initKeywords() +{ + // SQLite 3 + keywords3["REINDEX"] = TK3_REINDEX; + keywords3["INDEXED"] = TK3_INDEXED; + keywords3["INDEX"] = TK3_INDEX; + keywords3["DESC"] = TK3_DESC; + keywords3["ESCAPE"] = TK3_ESCAPE; + keywords3["EACH"] = TK3_EACH; + keywords3["CHECK"] = TK3_CHECK; + keywords3["KEY"] = TK3_KEY; + keywords3["BEFORE"] = TK3_BEFORE; + keywords3["FOREIGN"] = TK3_FOREIGN; + keywords3["FOR"] = TK3_FOR; + keywords3["IGNORE"] = TK3_IGNORE; + keywords3["REGEXP"] = TK3_LIKE_KW; + keywords3["EXPLAIN"] = TK3_EXPLAIN; + keywords3["INSTEAD"] = TK3_INSTEAD; + keywords3["ADD"] = TK3_ADD; + keywords3["DATABASE"] = TK3_DATABASE; + keywords3["AS"] = TK3_AS; + keywords3["SELECT"] = TK3_SELECT; + keywords3["TABLE"] = TK3_TABLE; + keywords3["LEFT"] = TK3_JOIN_KW; + keywords3["THEN"] = TK3_THEN; + keywords3["END"] = TK3_END; + keywords3["DEFERRABLE"] = TK3_DEFERRABLE; + keywords3["ELSE"] = TK3_ELSE; + keywords3["EXCEPT"] = TK3_EXCEPT; + keywords3["TRANSACTION"] = TK3_TRANSACTION; + keywords3["ACTION"] = TK3_ACTION; + keywords3["ON"] = TK3_ON; + keywords3["NATURAL"] = TK3_JOIN_KW; + keywords3["ALTER"] = TK3_ALTER; + keywords3["RAISE"] = TK3_RAISE; + keywords3["EXCLUSIVE"] = TK3_EXCLUSIVE; + keywords3["EXISTS"] = TK3_EXISTS; + keywords3["SAVEPOINT"] = TK3_SAVEPOINT; + keywords3["INTERSECT"] = TK3_INTERSECT; + keywords3["TRIGGER"] = TK3_TRIGGER; + keywords3["REFERENCES"] = TK3_REFERENCES; + keywords3["CONSTRAINT"] = TK3_CONSTRAINT; + keywords3["INTO"] = TK3_INTO; + keywords3["OFFSET"] = TK3_OFFSET; + keywords3["OF"] = TK3_OF; + keywords3["SET"] = TK3_SET; + keywords3["TEMP"] = TK3_TEMP; + keywords3["TEMPORARY"] = TK3_TEMP; + keywords3["OR"] = TK3_OR; + keywords3["UNIQUE"] = TK3_UNIQUE; + keywords3["QUERY"] = TK3_QUERY; + keywords3["ATTACH"] = TK3_ATTACH; + keywords3["HAVING"] = TK3_HAVING; + keywords3["GROUP"] = TK3_GROUP; + keywords3["UPDATE"] = TK3_UPDATE; + keywords3["BEGIN"] = TK3_BEGIN; + keywords3["INNER"] = TK3_JOIN_KW; + keywords3["RELEASE"] = TK3_RELEASE; + keywords3["BETWEEN"] = TK3_BETWEEN; + keywords3["NOTNULL"] = TK3_NOTNULL; + keywords3["NOT"] = TK3_NOT; + keywords3["NO"] = TK3_NO; + keywords3["NULL"] = TK3_NULL; + keywords3["LIKE"] = TK3_LIKE_KW; + keywords3["CASCADE"] = TK3_CASCADE; + keywords3["ASC"] = TK3_ASC; + keywords3["DELETE"] = TK3_DELETE; + keywords3["CASE"] = TK3_CASE; + keywords3["COLLATE"] = TK3_COLLATE; + keywords3["CREATE"] = TK3_CREATE; + keywords3["CURRENT_DATE"] = TK3_CTIME_KW; + keywords3["DETACH"] = TK3_DETACH; + keywords3["IMMEDIATE"] = TK3_IMMEDIATE; + keywords3["JOIN"] = TK3_JOIN; + keywords3["INSERT"] = TK3_INSERT; + keywords3["MATCH"] = TK3_MATCH; + keywords3["PLAN"] = TK3_PLAN; + keywords3["ANALYZE"] = TK3_ANALYZE; + keywords3["PRAGMA"] = TK3_PRAGMA; + keywords3["ABORT"] = TK3_ABORT; + keywords3["VALUES"] = TK3_VALUES; + keywords3["VIRTUAL"] = TK3_VIRTUAL; + keywords3["LIMIT"] = TK3_LIMIT; + keywords3["WHEN"] = TK3_WHEN; + keywords3["WHERE"] = TK3_WHERE; + keywords3["RENAME"] = TK3_RENAME; + keywords3["AFTER"] = TK3_AFTER; + keywords3["REPLACE"] = TK3_REPLACE; + keywords3["AND"] = TK3_AND; + keywords3["DEFAULT"] = TK3_DEFAULT; + keywords3["AUTOINCREMENT"] = TK3_AUTOINCR; + keywords3["TO"] = TK3_TO; + keywords3["IN"] = TK3_IN; + keywords3["CAST"] = TK3_CAST; + keywords3["COLUMN"] = TK3_COLUMNKW; + keywords3["COMMIT"] = TK3_COMMIT; + keywords3["CONFLICT"] = TK3_CONFLICT; + keywords3["CROSS"] = TK3_JOIN_KW; + keywords3["CURRENT_TIMESTAMP"] = TK3_CTIME_KW; + keywords3["CURRENT_TIME"] = TK3_CTIME_KW; + keywords3["PRIMARY"] = TK3_PRIMARY; + keywords3["DEFERRED"] = TK3_DEFERRED; + keywords3["DISTINCT"] = TK3_DISTINCT; + keywords3["IS"] = TK3_IS; + keywords3["DROP"] = TK3_DROP; + keywords3["FAIL"] = TK3_FAIL; + keywords3["FROM"] = TK3_FROM; + keywords3["FULL"] = TK3_JOIN_KW; + keywords3["GLOB"] = TK3_LIKE_KW; + keywords3["BY"] = TK3_BY; + keywords3["IF"] = TK3_IF; + keywords3["ISNULL"] = TK3_ISNULL; + keywords3["ORDER"] = TK3_ORDER; + keywords3["RESTRICT"] = TK3_RESTRICT; + keywords3["OUTER"] = TK3_JOIN_KW; + keywords3["RIGHT"] = TK3_JOIN_KW; + keywords3["ROLLBACK"] = TK3_ROLLBACK; + keywords3["ROW"] = TK3_ROW; + keywords3["UNION"] = TK3_UNION; + keywords3["USING"] = TK3_USING; + keywords3["VACUUM"] = TK3_VACUUM; + keywords3["VIEW"] = TK3_VIEW; + keywords3["INITIALLY"] = TK3_INITIALLY; + keywords3["WITHOUT"] = TK3_WITHOUT; + keywords3["ALL"] = TK3_ALL; + keywords3["WITH"] = TK3_WITH; + keywords3["RECURSIVE"] = TK3_RECURSIVE; + + // SQLite 2 + keywords2["ABORT"] = TK2_ABORT; + keywords2["AFTER"] = TK2_AFTER; + keywords2["ALL"] = TK2_ALL; + keywords2["AND"] = TK2_AND; + keywords2["AS"] = TK2_AS; + keywords2["ASC"] = TK2_ASC; + keywords2["ATTACH"] = TK2_ATTACH; + keywords2["BEFORE"] = TK2_BEFORE; + keywords2["BEGIN"] = TK2_BEGIN; + keywords2["BETWEEN"] = TK2_BETWEEN; + keywords2["BY"] = TK2_BY; + keywords2["CASCADE"] = TK2_CASCADE; + keywords2["CASE"] = TK2_CASE; + keywords2["CHECK"] = TK2_CHECK; + keywords2["CLUSTER"] = TK2_CLUSTER; + keywords2["COLLATE"] = TK2_COLLATE; + keywords2["COMMIT"] = TK2_COMMIT; + keywords2["CONFLICT"] = TK2_CONFLICT; + keywords2["CONSTRAINT"] = TK2_CONSTRAINT; + keywords2["COPY"] = TK2_COPY; + keywords2["CREATE"] = TK2_CREATE; + keywords2["CROSS"] = TK2_JOIN_KW; + keywords2["DATABASE"] = TK2_DATABASE; + keywords2["DEFAULT"] = TK2_DEFAULT; + keywords2["DEFERRED"] = TK2_DEFERRED; + keywords2["DEFERRABLE"] = TK2_DEFERRABLE; + keywords2["DELETE"] = TK2_DELETE; + keywords2["DELIMITERS"] = TK2_DELIMITERS; + keywords2["DESC"] = TK2_DESC; + keywords2["DETACH"] = TK2_DETACH; + keywords2["DISTINCT"] = TK2_DISTINCT; + keywords2["DROP"] = TK2_DROP; + keywords2["END"] = TK2_END; + keywords2["EACH"] = TK2_EACH; + keywords2["ELSE"] = TK2_ELSE; + keywords2["EXCEPT"] = TK2_EXCEPT; + keywords2["EXPLAIN"] = TK2_EXPLAIN; + keywords2["FAIL"] = TK2_FAIL; + keywords2["FOR"] = TK2_FOR; + keywords2["FOREIGN"] = TK2_FOREIGN; + keywords2["FROM"] = TK2_FROM; + keywords2["FULL"] = TK2_JOIN_KW; + keywords2["GLOB"] = TK2_GLOB; + keywords2["GROUP"] = TK2_GROUP; + keywords2["HAVING"] = TK2_HAVING; + keywords2["IGNORE"] = TK2_IGNORE; + keywords2["IMMEDIATE"] = TK2_IMMEDIATE; + keywords2["IN"] = TK2_IN; + keywords2["INDEX"] = TK2_INDEX; + keywords2["INITIALLY"] = TK2_INITIALLY; + keywords2["INNER"] = TK2_JOIN_KW; + keywords2["INSERT"] = TK2_INSERT; + keywords2["INSTEAD"] = TK2_INSTEAD; + keywords2["INTERSECT"] = TK2_INTERSECT; + keywords2["INTO"] = TK2_INTO; + keywords2["IS"] = TK2_IS; + keywords2["ISNULL"] = TK2_ISNULL; + keywords2["JOIN"] = TK2_JOIN; + keywords2["KEY"] = TK2_KEY; + keywords2["LEFT"] = TK2_JOIN_KW; + keywords2["LIKE"] = TK2_LIKE; + keywords2["LIMIT"] = TK2_LIMIT; + keywords2["MATCH"] = TK2_MATCH; + keywords2["NATURAL"] = TK2_JOIN_KW; + keywords2["NOT"] = TK2_NOT; + keywords2["NOTNULL"] = TK2_NOTNULL; + keywords2["NULL"] = TK2_NULL; + keywords2["OF"] = TK2_OF; + keywords2["OFFSET"] = TK2_OFFSET; + keywords2["ON"] = TK2_ON; + keywords2["OR"] = TK2_OR; + keywords2["ORDER"] = TK2_ORDER; + keywords2["OUTER"] = TK2_JOIN_KW; + keywords2["PRAGMA"] = TK2_PRAGMA; + keywords2["PRIMARY"] = TK2_PRIMARY; + keywords2["RAISE"] = TK2_RAISE; + keywords2["REFERENCES"] = TK2_REFERENCES; + keywords2["REPLACE"] = TK2_REPLACE; + keywords2["RESTRICT"] = TK2_RESTRICT; + keywords2["RIGHT"] = TK2_JOIN_KW; + keywords2["ROLLBACK"] = TK2_ROLLBACK; + keywords2["ROW"] = TK2_ROW; + keywords2["SELECT"] = TK2_SELECT; + keywords2["SET"] = TK2_SET; + keywords2["STATEMENT"] = TK2_STATEMENT; + keywords2["TABLE"] = TK2_TABLE; + keywords2["TEMP"] = TK2_TEMP; + keywords2["TEMPORARY"] = TK2_TEMP; + keywords2["THEN"] = TK2_THEN; + keywords2["TRANSACTION"] = TK2_TRANSACTION; + keywords2["TRIGGER"] = TK2_TRIGGER; + keywords2["UNION"] = TK2_UNION; + keywords2["UNIQUE"] = TK2_UNIQUE; + keywords2["UPDATE"] = TK2_UPDATE; + keywords2["USING"] = TK2_USING; + keywords2["VACUUM"] = TK2_VACUUM; + keywords2["VALUES"] = TK2_VALUES; + keywords2["VIEW"] = TK2_VIEW; + keywords2["WHEN"] = TK2_WHEN; + keywords2["WHERE"] = TK2_WHERE; + + rowIdKeywords << "_ROWID_" + << "ROWID" + << "OID"; + + + joinKeywords << "NATURAL" << "LEFT" << "RIGHT" << "OUTER" << "INNER" << "CROSS"; + fkMatchKeywords << "SIMPLE" << "FULL" << "PARTIAL"; + conflictAlgoKeywords << "ROLLBACK" << "ABORT" << "FAIL" << "IGNORE" << "REPLACE"; +} + + +bool isJoinKeyword(const QString &str) +{ + return joinKeywords.contains(str, Qt::CaseInsensitive); +} + +QStringList getJoinKeywords() +{ + return joinKeywords; +} + +QStringList getFkMatchKeywords() +{ + return fkMatchKeywords; +} + +bool isFkMatchKeyword(const QString &str) +{ + return fkMatchKeywords.contains(str); +} + + +bool isKeyword(const QString& str, Dialect dialect) +{ + switch (dialect) + { + case Dialect::Sqlite3: + return keywords3.contains(str.toUpper()); + case Dialect::Sqlite2: + return keywords2.contains(str.toUpper()); + } + return false; +} + +QStringList getConflictAlgorithms() +{ + return conflictAlgoKeywords; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/keywords.h b/SQLiteStudio3/coreSQLiteStudio/parser/keywords.h new file mode 100644 index 0000000..a6e1d3d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/keywords.h @@ -0,0 +1,123 @@ +#ifndef KEYWORDS_H +#define KEYWORDS_H + +#include "dialect.h" +#include "coreSQLiteStudio_global.h" +#include +#include +#include + +/** @file */ + +/** + * @brief Translates keyword into it's Lemon token ID for SQLite 2 dialect. + * @param str The keyword. + * @return Lemon generated token ID, or TK2_ID value when the \p str parameter was not recognized as a valid SQLite 2 keyword. + * + * This method is used internally by the Lexer. + * Comparision is done in case insensitive manner. + */ +API_EXPORT int getKeywordId2(const QString& str); + +/** + * @brief Translates keyword into it's Lemon token ID for SQLite 3 dialect. + * @param str The keyword. + * @return Lemon generated token ID, or TK3_ID value when the \p str parameter was not recognized as a valid SQLite 3 keyword. + * + * This method is used internally by the Lexer. + * Comparision is done in case insensitive manner. + */ +API_EXPORT int getKeywordId3(const QString& str); + +/** + * @brief Tests whether given string represents a keyword in given SQLite dialect. + * @param str String to test. + * @param dialect SQLite dialect. + * @return true if the string represents a keyword, or false otherwise. + * + * Comparision is done in case insensitive manner. + */ +API_EXPORT bool isKeyword(const QString& str, Dialect dialect); + +/** + * @brief Tests whether given string representing any variation of ROWID. + * @param str String to test. + * @return true if the value represents ROWID keyword, or false otherwise. + * + * ROWID keywords understood by SQLite are: ROWID, _ROWID_ and OID. + * Comparision is done in case insensitive manner. + */ +API_EXPORT bool isRowIdKeyword(const QString& str); + +/** + * @brief Provides map of SQLite 2 keywords and their Lemon token IDs. + * @return Keyword-to-Lemon-ID hash map, keywords are uppercase. + */ +API_EXPORT const QHash& getKeywords2(); + +/** + * @brief Provides map of SQLite 3 keywords and their Lemon token IDs. + * @return Keyword-to-Lemon-ID hash map, keywords are uppercase. + */ +API_EXPORT const QHash& getKeywords3(); + +/** + * @brief Provides list of keywords representing types of SQL joins. + * @return Join type keywords. + * + * Join type keywords are: NATURAL, LEFT, RIGHT, OUTER, INNER, CROSS. + * + * Join type keywords are distinguished from other keywords, because SQLite grammar definitions + * allow those keywords to be used in contexts other than just join type definition. + */ +API_EXPORT QStringList getJoinKeywords(); + +/** + * @brief Tests whether the keyword is one of join type keywords. + * @param str String to test. + * @return true if the value represents join type keyword. + * + * This method simply tests if given string is on the list returned from getJoinKeywords(). + * + * Comparision is done in case insensitive manner. + */ +API_EXPORT bool isJoinKeyword(const QString& str); + +/** + * @brief Returns foreign key "match type" keywords. + * @return Match type keywords. + * + * Match type keywords are: SIMPLE, FULL and PARTIAL. + * + * Foreign key match type keywords are distinguished from other keywords, because SQLite grammar + * definitions allow those keywords to be used in contexts other than just foreign key match type definition. + */ +API_EXPORT QStringList getFkMatchKeywords(); + +/** + * @brief Tests whether the given value is one of match type keywords. + * @param str String to test. + * @return true if the value represents match type keywords, false otherwise. + * + * Comparision is done in case insensitive manner. + */ +API_EXPORT bool isFkMatchKeyword(const QString& str); + +/** + * @brief Initializes internal tables of keywords. + * + * This has to be (and it is) done at application startup. It defines all internal hash tables + * and lists with keywords. + */ +API_EXPORT void initKeywords(); + +/** + * @brief Provides list of SQLite conflict algorithm keywords. + * + * Conflict algorithms keywords are: ROLLBACK, ABORT, FAIL, IGNORE, REPLACE. + * + * Those keywords are used for example on GUI, when user has an "On conflict" algorithm to pick from drop-down list. + */ +API_EXPORT QStringList getConflictAlgorithms(); + +#endif // KEYWORDS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lempar.c b/SQLiteStudio3/coreSQLiteStudio/parser/lempar.c new file mode 100644 index 0000000..4f9cd5c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lempar.c @@ -0,0 +1,1021 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +** +** This version of "lempar.c" is modified, slightly, for use by SQLite. +** The only modifications are the addition of a couple of NEVER() +** macros to disable tests that are needed in the case of a general +** LALR(1) grammar but which are always false in the +** specific grammar used by SQLite. +*/ +/* First off, code is included that follows the "include" declaration +** in the input grammar file. */ +#include +%% +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +%% +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** ParseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is ParseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** ParseARG_SDECL A static variable declaration for the %extra_argument +** ParseARG_PDECL A parameter declaration for the %extra_argument +** ParseARG_STORE Code to store %extra_argument into yypParser +** ParseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +%% +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +#define GET_CONTEXT yyParser* yypParser = pParser; ParseARG_FETCH + +/* The yyzerominor constant is used to initialize instances of +** YYMINORTYPE objects to zero. */ +static const YYMINORTYPE yyzerominor = { 0 }; + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +%% + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { +%% +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ + QList* tokens = nullptr; +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyidxMax; /* Maximum value of yyidx */ +#endif + int yyerrcnt; /* Shifts left before out of the error */ + ParseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +void *ParseCopyParserState(void* other) +{ + yyParser *pParser; + yyParser *otherParser = (yyParser*)other; + + // Copy parser + pParser = (yyParser*)malloc((size_t)sizeof(yyParser)); + memcpy(pParser, other, (size_t)sizeof(yyParser)); + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = malloc((size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)other)->yystack, (size_t)stackSize); +#endif + + for (int i = 0; i <= pParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList(); + *(pParser->yystack[i].tokens) = *(otherParser->yystack[i].tokens); + } + + return pParser; +} + +void ParseAddToken(void* other, Token* token) +{ + yyParser *otherParser = (yyParser*)other; + if (otherParser->yyidx < 0) + return; // Nothing on stack yet. Might happen when parsing just whitespaces, nothing else. + + otherParser->yystack[otherParser->yyidx].tokens->append(token); +} + +void ParseRestoreParserState(void* saved, void* target) +{ + yyParser *pParser = (yyParser*)target; + yyParser *savedParser = (yyParser*)saved; + + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + + memcpy(pParser, saved, (size_t)sizeof(yyParser)); + + for (int i = 0; i <= savedParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList(); + *(pParser->yystack[i].tokens) = *(savedParser->yystack[i].tokens); + } + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = relloc(pParser->yystack, (size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)saved)->yystack, (size_t)stackSize); +#endif +} + +void ParseFreeSavedState(void* other) +{ + yyParser *pParser = (yyParser*)other; + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + free(other); +} + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
    +**
  • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
  • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
+** +** Outputs: +** None. +*/ +void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { +%% +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { +%% +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. +*/ +static void yyGrowStack(yyParser *p){ + int newSize; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + if( pNew ){ + p->yystack = pNew; + p->yystksz = newSize; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", + yyTracePrompt, p->yystksz); + } +#endif + } +} +#endif + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to Parse and ParseFree. +*/ +void *ParseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyidxMax = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yystack = NULL; + pParser->yystksz = 0; + yyGrowStack(pParser); +#endif + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + ParseARG_FETCH; + if (parserContext->executeRules) + { + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ +%% + default: break; /* If no destructor action specified: do nothing */ + } + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + /* There is no mechanism by which the parser stack can be popped below + ** empty in SQLite. */ + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor(pParser, yymajor, &yytos->minor); + delete yytos->tokens; + yytos->tokens = nullptr; + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +**
    +**
  • A pointer to the parser. This should be a pointer +** obtained from ParseAlloc. +**
  • A pointer to a function used to reclaim memory obtained +** from malloc. +**
+*/ +void ParseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + /* In SQLite, we never try to destroy a parser that was not successfully + ** created in the first place. */ + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int ParseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyidxMax; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + GET_CONTEXT; + + if( stateno>YY_SHIFT_COUNT + || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAheaddoFallbacks ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + int stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( i!=YY_REDUCE_USE_DFLT ); + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && iyyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH ){ + yyStackOverflow(yypParser, yypMinor); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz ){ + yyStackOverflow(yypParser, yypMinor); + return; + } + } +#endif + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor = *yypMinor; + yytos->tokens = new QList(); +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { +%% +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + ParseARG_FETCH; + SqliteStatement* objectForTokens = 0; + QStringList noTokenInheritanceFields; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + /* Silence complaints from purify about yygotominor being uninitialized + ** in some cases when it is copied into the stack after the following + ** switch. yygotominor is uninitialized when a rule reduces that does + ** not set the value of its left-hand side nonterminal. Leaving the + ** value of the nonterminal uninitialized is utterly harmless as long + ** as the value is never used. So really the only thing this code + ** accomplishes is to quieten purify. + ** + ** 2007-01-16: The wireshark project (www.wireshark.org) reports that + ** without this code, their parser segfaults. I'm not sure what there + ** parser is doing to make this happen. This is the second bug report + ** from wireshark this week. Clearly they are stressing Lemon in ways + ** that it has not been previously stressed... (SQLite ticket #2172) + */ + /*memset(&yygotominor, 0, sizeof(yygotominor));*/ + yygotominor = yyzerominor; + + + if (parserContext->executeRules) + { + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ +%% + }; + } + assert( yyruleno>=0 && yyruleno<(int)(sizeof(yyRuleInfo)/sizeof(yyRuleInfo[0])) ); + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + + // Store tokens for the rule in parser context + QList allTokens; + QList allTokensWithAllInherited; + QString keyForTokensMap; + int tokensMapKeyCnt; + if (parserContext->setupTokens) + { + if (objectForTokens) + { + // In case this is a list with recurrent references we need + // to clear tokens before adding the new and extended list. + objectForTokens->tokens.clear(); + } + + QList tokens; + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + tokens.clear(); + const char* fieldName = yyTokenName[yypParser->yystack[i].major]; + if (parserContext->isManagedToken(yypParser->yystack[i].minor.yy0)) + tokens += yypParser->yystack[i].minor.yy0; + + tokens += *(yypParser->yystack[i].tokens); + + if (!noTokenInheritanceFields.contains(fieldName)) + { + if (objectForTokens) + { + keyForTokensMap = fieldName; + tokensMapKeyCnt = 2; + while (objectForTokens->tokensMap.contains(keyForTokensMap)) + keyForTokensMap = fieldName + QString::number(tokensMapKeyCnt++); + + objectForTokens->tokensMap[keyForTokensMap] = parserContext->getTokenPtrList(tokens); + } + + allTokens += tokens; + } + else + { + // If field is mentioned only once, then only one occurance of it will be ignored. + // Second one should be inherited. See "anylist" definition for explanation why. + noTokenInheritanceFields.removeOne(fieldName); + } + allTokensWithAllInherited += tokens; + } + if (objectForTokens) + { + objectForTokens->tokens += parserContext->getTokenPtrList(allTokens); + } + } + + // Clear token lists + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + delete yypParser->yystack[i].tokens; + yypParser->yystack[i].tokens = nullptr; + } + + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); + if( yyact < YYNSTATE ){ +#ifdef NDEBUG + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if( yysize ){ + yypParser->yyidx++; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yymsp->minor = yygotominor; + if (parserContext->setupTokens) + *(yypParser->yystack[yypParser->yyidx].tokens) = allTokens; + }else +#endif + { + yy_shift(yypParser,yyact,yygoto,&yygotominor); + if (parserContext->setupTokens) + { + QList* tokensPtr = yypParser->yystack[yypParser->yyidx].tokens; + *tokensPtr = allTokensWithAllInherited + *tokensPtr; + } + } + }else{ + assert( yyact == YYNSTATE + YYNRULE + 1 ); + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + ParseARG_FETCH; +#define TOKEN (yyminor.yy0) +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "ParseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
    +**
  • A pointer to the parser (an opaque structure.) +**
  • The major token number. +**
  • The minor token number. +**
  • An option argument of a grammar-specified type. +**
+** +** Outputs: +** None. +*/ +void Parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + ParseTOKENTYPE yyminor /* The value for the token */ + ParseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ +#if YYSTACKDEPTH<=0 + if( yypParser->yystksz <=0 ){ + /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ + yyminorunion = yyzerominor; + yyStackOverflow(yypParser, &yyminorunion); + return; + } +#endif + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + yypParser->yystack[0].tokens = new QList(); + } + yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + ParseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s [%s] (lemon type: %s)\n", + yyTracePrompt, + yyminor->value.toLatin1().data(), + yyminor->typeString().toLatin1().data(), + yyTokenName[yymajor]); } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyactyyerrcnt--; + yymajor = YYNOCODE; + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else{ + assert( yyact == YY_ERROR_ACTION ); +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_reduce_action( + yypParser->yystack[yypParser->yyidx].stateno, + YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yyerrorhit = 1; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp new file mode 100644 index 0000000..c85b3ba --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp @@ -0,0 +1,314 @@ +#include "lexer.h" +#include "keywords.h" +#include "lexer_low_lev.h" +#include "sqlite2_parse.h" +#include "sqlite3_parse.h" +#include +#include +#include + +QHash > Lexer::everyTokenType2; +QHash > Lexer::everyTokenType3; +QHash Lexer::everyTokenTypePtrMap; +TokenPtr Lexer::semicolonTokenSqlite2; +TokenPtr Lexer::semicolonTokenSqlite3; + +Lexer::Lexer(Dialect dialect) + : dialect(dialect), sqlToTokenize(QString::null) +{ +} + +Lexer::~Lexer() +{ + cleanUp(); +} + +TokenList Lexer::tokenize(const QString &sql) +{ + TokenList resultList; + int lgt; + TokenPtr token; + QString str = sql; + + quint64 pos = 0; + for (;str.size() > 0;) + { + if (tolerant) + token = TolerantTokenPtr::create(); + else + token = TokenPtr::create(); + + lgt = lexerGetToken(str, token, dialect == Dialect::Sqlite2 ? 2 : 3, tolerant); + if (lgt == 0) + break; + + token->value = str.mid(0, lgt); + token->start = pos; + token->end = pos + lgt - 1; + + resultList << token; + str = str.mid(lgt); + pos += lgt; + } + + return resultList; +} + +void Lexer::prepare(const QString &sql) +{ + sqlToTokenize = sql; + tokenPosition = 0; +} + +TokenPtr Lexer::getToken() +{ + if (sqlToTokenize.isEmpty()) + return TokenPtr(); + + TokenPtr token; + if (tolerant) + token = TolerantTokenPtr::create(); + else + token = TokenPtr::create(); + + int lgt = lexerGetToken(sqlToTokenize, token, dialect == Dialect::Sqlite2 ? 2 : 3, tolerant); + if (lgt == 0) + return TokenPtr(); + + token->value = sqlToTokenize.mid(0, lgt); + token->start = tokenPosition; + token->end = tokenPosition + lgt - 1; + + sqlToTokenize = sqlToTokenize.mid(lgt); + tokenPosition += lgt; + + return token; +} + +void Lexer::cleanUp() +{ + sqlToTokenize.clear(); + tokenPosition = 0; +} + +void Lexer::setTolerantMode(bool enabled) +{ + tolerant = enabled; +} + +QSet Lexer::getEveryTokenType() +{ + return getEveryTokenType({Token::BIND_PARAM, Token::BLOB, Token::COMMENT, Token::FLOAT, + Token::INTEGER, Token::KEYWORD, Token::OPERATOR, Token::OTHER, + Token::PAR_LEFT, Token::PAR_RIGHT, Token::SPACE, Token::STRING, + Token::INVALID}); +} + +QSet Lexer::getEveryTokenType(QSet types) +{ + // Process set of types + QSet results; + QHashIterator > i( + dialect == Dialect::Sqlite2 ? everyTokenType2 : everyTokenType3 + ); + while (i.hasNext()) + { + i.next(); + if (types.contains(i.key())) + { + QSet tk = i.value(); + results.unite(tk); + } + } + return results; +} + +bool Lexer::isEnd() const +{ + return sqlToTokenize.isEmpty(); +} + +TokenPtr Lexer::getSemicolonToken(Dialect dialect) +{ + return (dialect == Dialect::Sqlite3) ? semicolonTokenSqlite3 : semicolonTokenSqlite2; +} + +void Lexer::staticInit() +{ + createTokenType(Dialect::Sqlite3, TK3_SPACE, Token::SPACE, " "); + createTokenType(Dialect::Sqlite3, TK3_COMMENT, Token::COMMENT, "--"); + createTokenType(Dialect::Sqlite3, TK3_MINUS, Token::OPERATOR, "-"); + createTokenType(Dialect::Sqlite3, TK3_SPACE, Token::SPACE, " "); + createTokenType(Dialect::Sqlite3, TK3_LP, Token::PAR_LEFT, "("); + createTokenType(Dialect::Sqlite3, TK3_RP, Token::PAR_RIGHT, ")"); + semicolonTokenSqlite3 = + createTokenType(Dialect::Sqlite3, TK3_SEMI, Token::OPERATOR, ";"); + createTokenType(Dialect::Sqlite3, TK3_PLUS, Token::OPERATOR, "+"); + createTokenType(Dialect::Sqlite3, TK3_STAR, Token::OPERATOR, "*"); + createTokenType(Dialect::Sqlite3, TK3_SLASH, Token::OPERATOR, "/"); + createTokenType(Dialect::Sqlite3, TK3_COMMENT, Token::COMMENT, "/* */"); + createTokenType(Dialect::Sqlite3, TK3_EQ, Token::OPERATOR, "="); + createTokenType(Dialect::Sqlite3, TK3_EQ, Token::OPERATOR, "=="); + createTokenType(Dialect::Sqlite3, TK3_LE, Token::OPERATOR, "<="); + createTokenType(Dialect::Sqlite3, TK3_NE, Token::OPERATOR, "<>"); + createTokenType(Dialect::Sqlite3, TK3_NE, Token::OPERATOR, "!="); + createTokenType(Dialect::Sqlite3, TK3_LSHIFT, Token::OPERATOR, "<<"); + createTokenType(Dialect::Sqlite3, TK3_LT, Token::OPERATOR, "<"); + createTokenType(Dialect::Sqlite3, TK3_GE, Token::OPERATOR, ">="); + createTokenType(Dialect::Sqlite3, TK3_RSHIFT, Token::OPERATOR, ">>"); + createTokenType(Dialect::Sqlite3, TK3_GT, Token::OPERATOR, ">"); + createTokenType(Dialect::Sqlite3, TK3_BITOR, Token::OPERATOR, "|"); + createTokenType(Dialect::Sqlite3, TK3_CONCAT, Token::OPERATOR, "||"); + createTokenType(Dialect::Sqlite3, TK3_COMMA, Token::OPERATOR, ","); + createTokenType(Dialect::Sqlite3, TK3_BITAND, Token::OPERATOR, "&"); + createTokenType(Dialect::Sqlite3, TK3_BITNOT, Token::OPERATOR, "~"); + createTokenType(Dialect::Sqlite3, TK3_STRING, Token::STRING, "' '"); + createTokenType(Dialect::Sqlite3, TK3_ID, Token::OTHER, "id"); + createTokenType(Dialect::Sqlite3, TK3_DOT, Token::OPERATOR, "."); + createTokenType(Dialect::Sqlite3, TK3_INTEGER, Token::INTEGER, "1"); + createTokenType(Dialect::Sqlite3, TK3_FLOAT, Token::FLOAT, "1.0"); + createTokenType(Dialect::Sqlite3, TK3_VARIABLE, Token::BIND_PARAM, "?"); + createTokenType(Dialect::Sqlite3, TK3_BLOB, Token::BLOB, "X'53'"); + + // Contextual ID tokens + createTokenType(Dialect::Sqlite3, TK3_ID_DB, Token::CTX_DATABASE, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TAB, Token::CTX_TABLE, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TAB_NEW, Token::CTX_TABLE_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_COL, Token::CTX_COLUMN, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_COL_NEW, Token::CTX_COLUMN_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_COL_TYPE, Token::CTX_COLUMN_TYPE, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_COLLATE, Token::CTX_COLLATION, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_FN, Token::CTX_FUNCTION, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_ERR_MSG, Token::CTX_ERROR_MESSAGE, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_IDX, Token::CTX_INDEX, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_IDX_NEW, Token::CTX_INDEX_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_VIEW, Token::CTX_VIEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_VIEW_NEW, Token::CTX_VIEW_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_JOIN_OPTS, Token::CTX_JOIN_OPTS, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_CONSTR, Token::CTX_CONSTRAINT, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_FK_MATCH, Token::CTX_FK_MATCH, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TRANS, Token::CTX_TRANSACTION, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_ALIAS, Token::CTX_ALIAS, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_PRAGMA, Token::CTX_PRAGMA, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TRIG, Token::CTX_TRIGGER, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TRIG_NEW, Token::CTX_TRIGGER_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_CTX_ROWID_KW, Token::CTX_ROWID_KW, "ROWID"); + createTokenType(Dialect::Sqlite3, TK3_ID, Token::CTX_OLD_KW, "OLD"); + createTokenType(Dialect::Sqlite3, TK3_ID, Token::CTX_NEW_KW, "NEW"); + + QHashIterator i3(getKeywords3()); + while (i3.hasNext()) + { + i3.next(); + createTokenType(Dialect::Sqlite3, i3.value(), Token::KEYWORD, i3.key()); + } + + // + // SQLite 2 + // + + createTokenType(Dialect::Sqlite2, TK2_SPACE, Token::SPACE, " "); + createTokenType(Dialect::Sqlite2, TK2_COMMENT, Token::COMMENT, "--"); + createTokenType(Dialect::Sqlite2, TK2_MINUS, Token::OPERATOR, "-"); + createTokenType(Dialect::Sqlite2, TK2_SPACE, Token::SPACE, " "); + createTokenType(Dialect::Sqlite2, TK2_LP, Token::PAR_LEFT, "("); + createTokenType(Dialect::Sqlite2, TK2_RP, Token::PAR_RIGHT, ")"); + semicolonTokenSqlite2 = + createTokenType(Dialect::Sqlite2, TK2_SEMI, Token::OPERATOR, ";"); + createTokenType(Dialect::Sqlite2, TK2_PLUS, Token::OPERATOR, "+"); + createTokenType(Dialect::Sqlite2, TK2_STAR, Token::OPERATOR, "*"); + createTokenType(Dialect::Sqlite2, TK2_SLASH, Token::OPERATOR, "/"); + createTokenType(Dialect::Sqlite2, TK2_COMMENT, Token::COMMENT, "/* */"); + createTokenType(Dialect::Sqlite2, TK2_EQ, Token::OPERATOR, "="); + createTokenType(Dialect::Sqlite2, TK2_EQ, Token::OPERATOR, "=="); + createTokenType(Dialect::Sqlite2, TK2_LE, Token::OPERATOR, "<="); + createTokenType(Dialect::Sqlite2, TK2_NE, Token::OPERATOR, "<>"); + createTokenType(Dialect::Sqlite2, TK2_NE, Token::OPERATOR, "!="); + createTokenType(Dialect::Sqlite2, TK2_LSHIFT, Token::OPERATOR, "<<"); + createTokenType(Dialect::Sqlite2, TK2_LT, Token::OPERATOR, "<"); + createTokenType(Dialect::Sqlite2, TK2_GE, Token::OPERATOR, ">="); + createTokenType(Dialect::Sqlite2, TK2_RSHIFT, Token::OPERATOR, ">>"); + createTokenType(Dialect::Sqlite2, TK2_GT, Token::OPERATOR, ">"); + createTokenType(Dialect::Sqlite2, TK2_BITOR, Token::OPERATOR, "|"); + createTokenType(Dialect::Sqlite2, TK2_CONCAT, Token::OPERATOR, "||"); + createTokenType(Dialect::Sqlite2, TK2_COMMA, Token::OPERATOR, ","); + createTokenType(Dialect::Sqlite2, TK2_BITAND, Token::OPERATOR, "&"); + createTokenType(Dialect::Sqlite2, TK2_BITNOT, Token::OPERATOR, "~"); + createTokenType(Dialect::Sqlite2, TK2_STRING, Token::STRING, "' '"); + createTokenType(Dialect::Sqlite2, TK2_ID, Token::OTHER, "id"); + createTokenType(Dialect::Sqlite2, TK2_DOT, Token::OPERATOR, "."); + createTokenType(Dialect::Sqlite2, TK2_INTEGER, Token::INTEGER, "1"); + createTokenType(Dialect::Sqlite2, TK2_FLOAT, Token::FLOAT, "1.0"); + createTokenType(Dialect::Sqlite2, TK2_VARIABLE, Token::BIND_PARAM, "?"); + + // Contextual ID tokens + createTokenType(Dialect::Sqlite2, TK2_ID_DB, Token::CTX_DATABASE, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TAB, Token::CTX_TABLE, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TAB_NEW, Token::CTX_TABLE_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_COL, Token::CTX_COLUMN, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_COL_NEW, Token::CTX_COLUMN_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_COL_TYPE, Token::CTX_COLUMN_TYPE, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_FN, Token::CTX_FUNCTION, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_ERR_MSG, Token::CTX_ERROR_MESSAGE, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_IDX, Token::CTX_INDEX, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_IDX_NEW, Token::CTX_INDEX_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_VIEW, Token::CTX_VIEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_VIEW_NEW, Token::CTX_VIEW_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_JOIN_OPTS, Token::CTX_JOIN_OPTS, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_CONSTR, Token::CTX_CONSTRAINT, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_FK_MATCH, Token::CTX_FK_MATCH, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TRANS, Token::CTX_TRANSACTION, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_ALIAS, Token::CTX_ALIAS, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_PRAGMA, Token::CTX_PRAGMA, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TRIG, Token::CTX_TRIGGER, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TRIG_NEW, Token::CTX_TRIGGER_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID, Token::CTX_ROWID_KW, "ROWID"); + createTokenType(Dialect::Sqlite2, TK2_ID, Token::CTX_OLD_KW, "OLD"); + createTokenType(Dialect::Sqlite2, TK2_ID, Token::CTX_NEW_KW, "NEW"); + + QHashIterator i2(getKeywords2()); + while (i2.hasNext()) + { + i2.next(); + createTokenType(Dialect::Sqlite2, i2.value(), Token::KEYWORD, i2.key()); + } +} + +QString Lexer::detokenize(const TokenList& tokens) +{ + if (tokens.size() == 0) + return ""; + + QString str; + foreach (TokenPtr token, tokens) + str += token->value; + + return str; +} + +TokenList Lexer::tokenize(const QString& sql, Dialect dialect) +{ + Lexer lexer(dialect); + return lexer.tokenize(sql); +} + +TokenPtr Lexer::getEveryTokenTypePtr(Token *token) +{ + if (everyTokenTypePtrMap.contains(token)) + return everyTokenTypePtrMap[token]; + + qDebug() << "Queried token not in Lexer::everyTokenTypePtrMap:" << token->toString(); + return TokenPtr(); +} + +TokenPtr Lexer::createTokenType(Dialect dialect, int lemonType, Token::Type type, const QString &value) +{ + TokenPtr tokenPtr = TokenPtr::create(lemonType, type, value, -100, -100); + if (dialect == Dialect::Sqlite2) + everyTokenType2[type] << tokenPtr; + else + everyTokenType3[type] << tokenPtr; + + everyTokenTypePtrMap[tokenPtr.data()] = tokenPtr; + return tokenPtr; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lexer.h b/SQLiteStudio3/coreSQLiteStudio/parser/lexer.h new file mode 100644 index 0000000..b21639e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lexer.h @@ -0,0 +1,254 @@ +#ifndef LEXER_H +#define LEXER_H + +#include "token.h" +#include "dialect.h" + +#include +#include +#include + +/** + * @brief Lexer for SQLite gramma. + * + * Lexer (aka tokenizer) splits SQL string into tokens. + * Tokens can be then used to syntax analysis, or for other purposes. + * + * It is useful if you have to modify some entities in the query, + * such as string, or object name, but you don't want to deal with + * all escape characters in the name, or other special characters. + * Lexer packs such entiries into separate tokens and gives them + * type, so you know what is the token representing. + */ +class API_EXPORT Lexer +{ + public: + /** + * @brief Creates lexer for given dialect. + * @param dialect SQLite dialect. + */ + Lexer(Dialect dialect); + + /** + * @brief Releases resources. + */ + virtual ~Lexer(); + + /** + * @brief Tokenizes (splits into tokens) given SQL query. + * @param sql SQL query to tokenize. + * @return List of tokens produced from tokenizing query. + */ + TokenList tokenize(const QString& sql); + + /** + * @brief Stores given SQL query internally for further processing by the lexer. + * @param sql Query to remember. + * + * This method should be followed by calls to getToken(). + */ + void prepare(const QString& sql); + + /** + * @brief Gets next token from query defined with prepare(). + * @return Token read from the query, or null token if no more tokens are available. + * + * Each call to this method generates token for next part of the query, not tokenized yet. + * Usual flow for this method looks like this: + * @code + * QString query = "..."; + * TokenPtr token; + * lexer.prepare(query); + * while (token = lexer.getToken()) + * { + * // do stuff with the token + * } + * @endcode + */ + TokenPtr getToken(); + + /** + * @brief Clears query stored with prepare(). + */ + void cleanUp(); + + /** + * @brief Enables or disabled tolerant mode. + * @param enabled If true, then all multi-line and unfinished tokens (strings, comments) will be reported + * with invalid=true in TolerantToken, but the token itself will have type like it was finished. + */ + void setTolerantMode(bool enabled); + + /** + * @brief Provides static sample tokens of all possible types. + * @return All possible token types. + * This method uses static set of tokens, so there's no need + * to delete them outside. + * + * It's used by Parser to try every token type as a possible candidate for a next valid token. + * You should not need to use this method. + */ + QSet getEveryTokenType(); + + /** + * @brief Gets static sample tokens of given types. + * @param types List of token types to get tokens for. Last element in the list must be Token::INVALID. + * + * It's used by Parser to try every token type as a possible candidate for a next valid token. + * You should not need to use this method. + * + * @overload + */ + QSet getEveryTokenType(QSet types); + + /** + * @brief Tests whether lexer finished reading all tokens from the query. + * @return true if there is no more tokens to be read, or false otherwise. + * + * This method simply checks whether there's any characters in the query to be tokenized. + * The query is the one defined with prepare(). Query shrinks with very call to getToken() + * and once there's no more characters to consume by getToken(), this method will return false. + * + * If you call getToken() after isEnd() returned false, the getToken() will return Token::INVALID token. + */ + bool isEnd() const; + + /** + * @brief Initializes internal set of static tokens. + * Initializes internal set of tokens used by getEveryTokenType(). + */ + static void staticInit(); + + /** + * @brief Restores string from token list. + * @param tokens List of tokens. + * @return String that was represented by tokens. + * + * It simply joins values of all tokens from the list using empty string separator (that is no separator at all). + */ + static QString detokenize(const TokenList& tokens); + + /** + * @brief Tokenizes given SQL query with given dialect. + * @param sql SQL query to tokenize. + * @param dialect SQLite dialect to use when tokenizing. + * @return List of tokens from tokenizing. + * + * This method is a shortcut for: + * @code + * Lexer lexer(dialect); + * lexer.tokenize(sql); + * @endcode + */ + static TokenList tokenize(const QString& sql, Dialect dialect); + + /** + * @brief Translates token pointer into common token shared pointer. + * @param token Token pointer to translate. + * @return Shared pointer if found, or null pointer if not found. + * + * This method should be used against token pointers extracted from getEveryTokenType() results. + * Then pointer from any TokenPtr (returned from getEveryTokenType()) is extracted using the + * QSharedPointer::data(), then this method can be used to return back to the QSharedPointer. + * + * As Lexer keeps static internal list of tokens representing token types, + * it can translate token pointer into shared pointer by comparing them. + * + * This method and getEveryTokenType() methods are used strictly by Parser and you should not + * need to use them. + */ + static TokenPtr getEveryTokenTypePtr(Token* token); + + /** + * @brief Provides token representing semicolon in given SQLite dialect. + * @param dialect Dialect to use. + * @return Token representing semicolon. + * + * This is used by Parser to complete the parsed query in case the input query did not end with semicolon. + * Given the \p dialect it provides proper token for that dialect (they are different by Lemon token ID). + */ + static TokenPtr getSemicolonToken(Dialect dialect); + + private: + /** + * @brief Creates token for every token type internal tables. + * @param dialect SQLite dialect to create token for. + * @param lemonType Lemon token ID for this token type. + * @param type SQLiteStudio token type. + * @param value Sample value for the token. + * @return Created token. + * + * Every token type internal tables are populated using this method. + * + * @see getEveryTokenType() + */ + static TokenPtr createTokenType(Dialect dialect, int lemonType, Token::Type type, const QString& value); + + /** + * @brief Current "tolerant mode" flag. + * + * @see setTolerantMode() + */ + bool tolerant = false; + + /** + * @brief Lexer's SQLite dialect. + */ + Dialect dialect; + + /** + * @brief SQL query to be tokenized with getToken(). + * + * It's defined with prepare(). + */ + QString sqlToTokenize; + + /** + * @brief Current tokenizer position in the sqlToTokenize. + * + * This position index is used to track which SQL characters should be tokenized + * on next call to getToken(). + * + * It's reset to 0 by prepare() and cleanUp(). + */ + quint64 tokenPosition; + + /** + * @brief Internal table of every token type for SQLite 2. + * + * @see semicolonTokenSqlite3 + */ + static TokenPtr semicolonTokenSqlite2; + + /** + * @brief Internal table of every token type for SQLite 3. + * + * Internal token type table contains single token per token type, so it can be used to probe the Parser + * for next valid token candidates. + */ + static TokenPtr semicolonTokenSqlite3; + + /** + * @brief Internal table of every token type for SQLite 2. + * + * @see everyTokenType3 + */ + static QHash > everyTokenType2; + + /** + * @brief Internal table of every token type for SQLite 3. + * + * Set of tokens representing all token types, including diversification by values for keywords and operators. + * It's used by the Parser to probe candidates for next valid token. + */ + static QHash > everyTokenType3; + + /** + * @brief Map of every token type pointer to its QSharedPointer from internal tables. + * + * This is used by getEveryTokenTypePtr(). + */ + static QHash everyTokenTypePtrMap; +}; + +#endif // LEXER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.cpp new file mode 100644 index 0000000..894afd1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.cpp @@ -0,0 +1,470 @@ +#include "lexer_low_lev.h" +#include "token.h" +#include "keywords.h" +#include "sqlite3_parse.h" +#include "sqlite2_parse.h" +#include "common/utils.h" +#include "common/utils_sql.h" + +#include +#include +#include + +// +// Low-level lexer routines based on tokenizer from SQLite 3.7.15.2 +// + +bool isIdChar(const QChar& c) +{ + return c.isPrint() && !c.isSpace() && !doesObjectNeedWrapping(c); +} + +int lexerGetToken(const QString& z, TokenPtr token, int sqliteVersion, bool tolerant) +{ + if (sqliteVersion < 2 || sqliteVersion > 3) + { + qCritical() << "lexerGetToken() called with invalid sqliteVersion:" << sqliteVersion; + return 0; + } + + if (tolerant && !token.dynamicCast()) + { + qCritical() << "lexerGetToken() called with tolerant=true, but not a TolerantToken entity!"; + return 0; + } + + bool v3 = sqliteVersion == 3; + int i; + QChar c; + QChar z0 = charAt(z, 0); + + for (;;) + { + if (z0.isSpace()) + { + for(i=1; charAt(z, i).isSpace(); i++) {} + token->lemonType = v3 ? TK3_SPACE : TK2_SPACE; + token->type = Token::SPACE; + return i; + } + if (z0 == '-') + { + if (charAt(z, 1) == '-') + { + for (i=2; (c = charAt(z, i)) != 0 && c != '\n'; i++) {} + token->lemonType = v3 ? TK3_COMMENT : TK2_COMMENT; + token->type = Token::COMMENT; + return i; + } + token->lemonType = v3 ? TK3_MINUS : TK2_MINUS; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '(') + { + token->lemonType = v3 ? TK3_LP : TK2_LP; + token->type = Token::PAR_LEFT; + return 1; + } + if (z0 == ')') + { + token->lemonType = v3 ? TK3_RP : TK2_RP; + token->type = Token::PAR_RIGHT; + return 1; + } + if (z0 == ';') + { + token->lemonType = v3 ? TK3_SEMI : TK2_SEMI; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '+') + { + token->lemonType = v3 ? TK3_PLUS : TK2_PLUS; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '*') + { + token->lemonType = v3 ? TK3_STAR : TK2_STAR; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '/') + { + if ( charAt(z, 1) != '*' ) + { + token->lemonType = v3 ? TK3_SLASH : TK2_SLASH; + token->type = Token::OPERATOR; + return 1; + } + + if ( charAt(z, 2) == 0 ) + { + token->lemonType = v3 ? TK3_COMMENT : TK2_COMMENT; + token->type = Token::COMMENT; + if (tolerant) + token.dynamicCast()->invalid = true; + + return 2; + } + for (i = 3, c = charAt(z, 2); (c != '*' || charAt(z, i) != '/') && (c = charAt(z, i)) != 0; i++) {} + + if (tolerant && (c != '*' || charAt(z, i) != '/')) + token.dynamicCast()->invalid = true; + + if ( c > 0 ) + i++; + token->lemonType = v3 ? TK3_COMMENT : TK2_COMMENT; + token->type = Token::COMMENT; + return i; + } + if (z0 == '%') + { + token->lemonType = v3 ? TK3_REM : TK2_REM; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '=') + { + token->lemonType = v3 ? TK3_EQ : TK2_EQ; + token->type = Token::OPERATOR; + return 1 + (charAt(z, 1) == '='); + } + if (z0 == '<') + { + if ( (c = charAt(z, 1)) == '=' ) + { + token->lemonType = v3 ? TK3_LE : TK2_LE; + token->type = Token::OPERATOR; + return 2; + } + else if ( c == '>' ) + { + token->lemonType = v3 ? TK3_NE : TK2_NE; + token->type = Token::OPERATOR; + return 2; + } + else if( c == '<' ) + { + token->lemonType = v3 ? TK3_LSHIFT : TK2_LSHIFT; + token->type = Token::OPERATOR; + return 2; + } + else + { + token->lemonType = v3 ? TK3_LT : TK2_LT; + token->type = Token::OPERATOR; + return 1; + } + } + if (z0 == '>') + { + if ( (c = charAt(z, 1)) == '=' ) + { + token->lemonType = v3 ? TK3_GE : TK2_GE; + token->type = Token::OPERATOR; + return 2; + } + else if ( c == '>' ) + { + token->lemonType = v3 ? TK3_RSHIFT : TK2_RSHIFT; + token->type = Token::OPERATOR; + return 2; + } + else + { + token->lemonType = v3 ? TK3_GT : TK2_GT; + token->type = Token::OPERATOR; + return 1; + } + } + if (z0 == '!') + { + if ( charAt(z, 1) != '=' ) + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + return 2; + } + else + { + token->lemonType = v3 ? TK3_NE : TK2_NE; + token->type = Token::OPERATOR; + return 2; + } + } + if (z0 == '|') + { + if( charAt(z, 1) != '|' ) + { + token->lemonType = v3 ? TK3_BITOR : TK2_BITOR; + token->type = Token::OPERATOR; + return 1; + } + else + { + token->lemonType = v3 ? TK3_CONCAT : TK2_CONCAT; + token->type = Token::OPERATOR; + return 2; + } + } + if (z0 == ',') + { + token->lemonType = v3 ? TK3_COMMA : TK2_COMMA; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '&') + { + token->lemonType = v3? TK3_BITAND : TK2_BITAND; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '~') + { + token->lemonType = v3? TK3_BITNOT : TK2_BITAND; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '`' || + z0 == '\'' || + z0 == '"') + { + QChar delim = z0; + for (i = 1; (c = charAt(z, i)) != 0; i++) + { + if ( c == delim ) + { + if( charAt(z, i+1) == delim ) + i++; + else + break; + } + } + if ( c == '\'' ) + { + token->lemonType = v3? TK3_STRING : TK2_STRING; + token->type = Token::STRING; + return i+1; + } + else if ( c != 0 ) + { + token->lemonType = v3 ? TK3_ID : TK2_ID; + token->type = Token::OTHER; + return i+1; + } + else if (tolerant) + { + if (z0 == '\'') + { + token->lemonType = v3 ? TK3_STRING : TK2_STRING; + token->type = Token::STRING; + } + else + { + token->lemonType = v3 ? TK3_ID : TK2_ID; + token->type = Token::OTHER; + } + token.dynamicCast()->invalid = true; + return i; + } + else + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + return i; + } + } + if (z0 == '.') + { + if( !charAt(z, 1).isDigit() ) + { + token->lemonType = v3 ? TK3_DOT : TK2_DOT; + token->type = Token::OPERATOR; + return 1; + } + /* + * If the next character is a digit, this is a floating point + * number that begins with ".". Fall thru into the next case + */ + } + if (z0.isDigit()) + { + token->lemonType = v3 ? TK3_INTEGER : TK2_INTEGER; + token->type = Token::INTEGER; + if (v3 && charAt(z, 0) == '0' && (charAt(z, 1) == 'x' || charAt(z, 1) == 'X') && isHex(charAt(z, 2))) + { + for (i=3; isHex(charAt(z, i)); i++) {} + return i; + } + for (i=0; charAt(z, i).isDigit(); i++) {} + if ( charAt(z, i) == '.' ) + { + i++; + while ( charAt(z, i).isDigit() ) + i++; + + token->lemonType = v3 ? TK3_FLOAT : TK2_FLOAT; + token->type = Token::FLOAT; + } + if ( (charAt(z, i) == 'e' || charAt(z, i) == 'E') && + ( charAt(z, i+1).isDigit() + || ((charAt(z, i+1) == '+' || charAt(z, i+1) == '-') && charAt(z, i+2).isDigit()) + ) + ) + { + i += 2; + while ( charAt(z, i+2).isDigit() ) + i++; + + token->lemonType = v3 ? TK3_FLOAT : TK2_FLOAT; + token->type = Token::FLOAT; + } + while ( isIdChar(charAt(z, i)) ) + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + i++; + } + return i; + } + if (z0 == '[') + { + for (i = 1, c = z0; c!=']' && (c = charAt(z, i)) != 0; i++) {} + if (c == ']') + { + token->lemonType = v3 ? TK3_ID : TK2_ID; + token->type = Token::OTHER; + } + else if (tolerant) + { + token->lemonType = v3 ? TK3_ID : TK2_ID; + token->type = Token::OTHER; + token.dynamicCast()->invalid = true; + } + else + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + } + return i; + } + if (z0 == '?') + { + token->lemonType = v3 ? TK3_VARIABLE : TK2_VARIABLE; + token->type = Token::BIND_PARAM; + for (i=1; charAt(z, i+2).isDigit(); i++) {} + return i; + } + if (z0 == '$' || + z0 == '@' || /* For compatibility with MS SQL Server */ + z0 == ':') + { + int n = 0; + token->lemonType = v3 ? TK3_VARIABLE : TK2_VARIABLE; + token->type = Token::BIND_PARAM; + for (i = 1; (c = charAt(z, i)) != 0; i++) + { + if ( isIdChar(c) ) + { + n++; + } + else if ( c == '(' && n > 0 ) + { + do + { + i++; + } + while ( (c = charAt(z, i)) != 0 && !c.isSpace() && c != ')' ); + + if ( c==')' ) + { + i++; + } + else + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + } + break; + } + else if ( c == ':' && charAt(z, i+1) == ':' ) + { + i++; + } + else + { + break; + } + } + if( n == 0 ) + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + } + + return i; + } + if ((z0 == 'x' || + z0 == 'X') && + v3) + { + if ( charAt(z, 1) == '\'' ) + { + token->lemonType = TK3_BLOB; + token->type = Token::BLOB; + for (i = 2; isXDigit(charAt(z, i)); i++) {} + if (charAt(z, i) != '\'' || i%2) + { + if (tolerant) + { + token->lemonType = TK3_BLOB; + token->type = Token::BLOB; + token.dynamicCast()->invalid = true; + } + else + { + token->lemonType = TK3_ILLEGAL; + token->type = Token::INVALID; + } + while (charAt(z, i) > 0 && charAt(z, i) != '\'') + i++; + } + if ( charAt(z, i) > 0 ) + i++; + + return i; + } + /* Otherwise fall through to the next case */ + } + //default: + { + if (!isIdChar(z0)) + break; + + for (i = 1; isIdChar(charAt(z, i)); i++) {} + + if (v3) + token->lemonType = getKeywordId3(z.mid(0, i)); + else + token->lemonType = getKeywordId2(z.mid(0, i)); + + if (token->lemonType == TK3_ID || token->lemonType == TK2_ID) + token->type = Token::OTHER; + else + token->type = Token::KEYWORD; + + return i; + } + } + + if (v3) + token->lemonType = TK3_ILLEGAL; + else + token->lemonType = TK2_ILLEGAL; + + token->type = Token::INVALID; + return 1; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.h b/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.h new file mode 100644 index 0000000..87ba7e5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.h @@ -0,0 +1,27 @@ +#ifndef LEXER_LOW_LEV_H +#define LEXER_LOW_LEV_H + +#include "parser/token.h" +#include + +/** @file */ + +/** + * @brief Low level tokenizer function used by the Lexer. + * @param z Query to tokenize. + * @param[out] token Token container to fill with values. Can be also a TolerantToken. + * @param sqliteVersion SQLite version, for which the tokenizer should work (2 or 3). + * Version affects the list of recognized keywords, a BLOB expression and an object name wrapper with the grave accent character (`). + * @param tolerant If true, then all multi-line and unfinished tokens (strings, comments) + * will be reported with invalid=true in TolerantToken, but the token itself will have type like it was finished. + * If this is true, then \p token must be of type TolerantToken, otherwise the the method will return 0 and log a critical error. + * @return Lemon token ID (see sqlite2_parse.h and sqlite3_parse.h for possible token IDs). + * + * You shouldn't normally need to use this method. Instead of that, use Lexer class, as it provides higher level API. + * + * Most of the method code was taken from SQLite tokenizer code. It is modified to support both SQLite 2 and 3 grammas + * and other SQLiteStudio specific features. + */ +int lexerGetToken(const QString& z, TokenPtr token, int sqliteVersion, bool tolerant = false); + +#endif // LEXER_LOW_LEV_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp new file mode 100644 index 0000000..23a4b55 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp @@ -0,0 +1,323 @@ +#include "parser.h" +#include "parsercontext.h" +#include "parsererror.h" +#include "lexer.h" +#include "../db/db.h" +#include "ast/sqliteselect.h" +#include +#include + +// Generated in sqlite*_parse.c by lemon, +// but not exported in any header +void* sqlite3_parseAlloc(void *(*mallocProc)(size_t)); +void sqlite3_parseFree(void *p, void (*freeProc)(void*)); +void sqlite3_parse(void *yyp, int yymajor, Token* yyminor, ParserContext* parserContext); +void sqlite3_parseTrace(FILE *stream, char *zPrefix); +void* sqlite3_parseCopyParserState(void* other); +void sqlite3_parseRestoreParserState(void* saved, void* target); +void sqlite3_parseFreeSavedState(void* other); +void sqlite3_parseAddToken(void* other, Token* token); + +void* sqlite2_parseAlloc(void *(*mallocProc)(size_t)); +void sqlite2_parseFree(void *p, void (*freeProc)(void*)); +void sqlite2_parse(void *yyp, int yymajor, Token* yyminor, ParserContext* parserContext); +void sqlite2_parseTrace(FILE *stream, char *zPrefix); +void* sqlite2_parseCopyParserState(void* other); +void sqlite2_parseRestoreParserState(void* saved, void* target); +void sqlite2_parseFreeSavedState(void* other); +void sqlite2_parseAddToken(void* other, Token* token); + +Parser::Parser(Dialect dialect) +{ + this->dialect = dialect; + init(); +} + +Parser::~Parser() +{ + cleanUp(); +} + +void Parser::cleanUp() +{ + if (lexer) + { + delete lexer; + lexer = nullptr; + } + + if (context) + { + delete context; + context = nullptr; + } +} + +void Parser::fillSqliteDialect() +{ + foreach (SqliteQueryPtr query, context->parsedQueries) + query->setSqliteDialect(dialect); +} + +void *Parser::parseAlloc(void *(*mallocProc)(size_t)) +{ + if (dialect == Dialect::Sqlite2) + return sqlite2_parseAlloc(mallocProc); + else + return sqlite3_parseAlloc(mallocProc); +} + +void Parser::parseFree(void *p, void (*freeProc)(void *)) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseFree(p, freeProc); + else + sqlite3_parseFree(p, freeProc); +} + +void Parser::parse(void *yyp, int yymajor, TokenPtr yyminor, ParserContext *parserContext) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parse(yyp, yymajor, yyminor.data(), parserContext); + else + sqlite3_parse(yyp, yymajor, yyminor.data(), parserContext); +} + +void Parser::parseTrace(FILE *stream, char *zPrefix) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseTrace(stream, zPrefix); + else + sqlite3_parseTrace(stream, zPrefix); +} + +void *Parser::parseCopyParserState(void *other) +{ + if (dialect == Dialect::Sqlite2) + return sqlite2_parseCopyParserState(other); + else + return sqlite3_parseCopyParserState(other); +} + +void Parser::parseRestoreParserState(void *saved, void *target) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseRestoreParserState(saved, target); + else + sqlite3_parseRestoreParserState(saved, target); +} + +void Parser::parseFreeSavedState(void *other) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseFreeSavedState(other); + else + sqlite3_parseFreeSavedState(other); +} + +void Parser::parseAddToken(void *other, TokenPtr token) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseAddToken(other, token.data()); + else + sqlite3_parseAddToken(other, token.data()); +} + +bool Parser::parse(const QString &sql, bool ignoreMinorErrors) +{ + context->ignoreMinorErrors = ignoreMinorErrors; + return parseInternal(sql, false); +} + +bool Parser::parseInternal(const QString &sql, bool lookForExpectedToken) +{ + void* pParser = parseAlloc( malloc ); + if (debugLemon) + { + char* label = nullptr; + if (dialect == Dialect::Sqlite2) + label = const_cast("[LEMON2]: "); + else + label = const_cast("[LEMON3]: "); + + parseTrace(stderr, label); + } + + reset(); + lexer->prepare(sql); + context->setupTokens = !lookForExpectedToken; + context->executeRules = !lookForExpectedToken; + context->doFallbacks = !lookForExpectedToken; + context->dialect = dialect; + + TokenPtr token = lexer->getToken(); + if (!token.isNull()) + context->addManagedToken(token); + + bool endsWithSemicolon = false; + + while (token) + { + if (token->type == Token::SPACE || + token->type == Token::COMMENT || + token->type == Token::INVALID) + { + parseAddToken(pParser, token); + token = lexer->getToken(); + if (token) + context->addManagedToken(token); + + continue; + } + + endsWithSemicolon = (token->type == Token::OPERATOR && token->value == ";"); + + parse(pParser, token->lemonType, token, context); + token = lexer->getToken(); + if (!token.isNull()) + context->addManagedToken(token); + } + + if (lookForExpectedToken) + { + expectedTokenLookup(pParser); + } + else + { + if (!endsWithSemicolon) + { + token = Lexer::getSemicolonToken(dialect); + parse(pParser, token->lemonType, token, context); + } + + qint64 endIdx = sql.length(); + TokenPtr endToken = TokenPtr::create(0, Token::INVALID, QString::null, endIdx, endIdx); + parse(pParser, 0, endToken, context); + } + + fillSqliteDialect(); + + // Free all non-termials having destructors + parseFree(pParser, free); + + context->flushErrors(); + + if (context->isSuccessful()) + { + for (SqliteQueryPtr query : context->parsedQueries) + query->processPostParsing(); + } + + return context->isSuccessful(); +} + +TokenList Parser::getNextTokenCandidates(const QString &sql) +{ + context->ignoreMinorErrors = true; + parseInternal(sql, true); + TokenList results = acceptedTokens; + acceptedTokens.clear(); + return results; +} + +bool Parser::isSuccessful() const +{ + return context->isSuccessful(); +} + +void Parser::reset() +{ + acceptedTokens.clear(); + lexer->cleanUp(); + context->cleanUp(); +} + +SqliteExpr *Parser::parseExpr(const QString &sql) +{ + SqliteSelectPtr select = parse("SELECT "+sql+";"); + if (!select || select->coreSelects.size() == 0 || select->coreSelects.first()->resultColumns.size() == 0) + return nullptr; + + SqliteExpr* expr = select->coreSelects.first()->resultColumns.first()->expr; + expr->setParent(nullptr); + return expr; +} + +void Parser::expectedTokenLookup(void* pParser) +{ + void* savedParser = parseCopyParserState(pParser); + + ParserContext tempContext; + tempContext.executeRules = false; + tempContext.executeRules = false; + tempContext.doFallbacks = false; + QSet tokenSet = + lexer->getEveryTokenType({ + Token::KEYWORD, Token::OTHER, Token::PAR_LEFT, Token::PAR_RIGHT, Token::OPERATOR, + Token::CTX_COLLATION, Token::CTX_COLUMN, Token::CTX_DATABASE, Token::CTX_FUNCTION, + Token::CTX_INDEX, Token::CTX_JOIN_OPTS, Token::CTX_TABLE, Token::CTX_TRIGGER, + Token::CTX_VIEW, Token::CTX_FK_MATCH, Token::CTX_ERROR_MESSAGE, Token::CTX_PRAGMA, + Token::CTX_ALIAS, Token::CTX_TABLE_NEW, Token::CTX_INDEX_NEW, Token::CTX_TRIGGER_NEW, + Token::CTX_VIEW_NEW, Token::CTX_COLUMN_NEW, Token::CTX_TRANSACTION, + Token::CTX_CONSTRAINT, Token::CTX_COLUMN_TYPE, Token::CTX_OLD_KW, Token::CTX_NEW_KW, + Token::CTX_ROWID_KW, Token::INVALID + }); + + foreach (TokenPtr token, tokenSet) + { + parse(pParser, token->lemonType, token, &tempContext); + + if (tempContext.isSuccessful()) + acceptedTokens += token; + + tempContext.cleanUp(); + parseRestoreParserState(savedParser, pParser); + } + parseFreeSavedState(savedParser); +} + +void Parser::init() +{ + lexer = new Lexer(dialect); + context = new ParserContext(); +} + +const QList &Parser::getErrors() +{ + return context->getErrors(); +} + +QString Parser::getErrorString() +{ + QStringList msgs; + foreach (ParserError* error, getErrors()) + { + msgs += error->getMessage(); + } + return msgs.join(",\n"); +} + +TokenList Parser::getParsedTokens() +{ + return context->getManagedTokens(); +} + +void Parser::setLemonDebug(bool enabled) +{ + debugLemon = enabled; +} + +void Parser::setDialect(Dialect dialect) +{ + if (this->dialect == dialect) + return; + + this->dialect = dialect; + delete lexer; + lexer = new Lexer(dialect); +} + +const QList& Parser::getQueries() +{ + return context->getQueries(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parser.h b/SQLiteStudio3/coreSQLiteStudio/parser/parser.h new file mode 100644 index 0000000..aaf3962 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parser.h @@ -0,0 +1,360 @@ +#ifndef PARSER_H +#define PARSER_H + +#include "token.h" +#include "../dialect.h" +#include "ast/sqlitequery.h" +#include "ast/sqliteexpr.h" + +class Lexer; +class ParserContext; +class ParserError; + +/** + * @brief SQL parser. + * + * The Parser analyzes given query and produces an Abstract Syntax Tree (AST). + * The AST is a tree of objects describing parsed query. + * + * Typical use case would be: + * @code + * Parser parser(db->getDialect()); + * if (parser.parse(queryString)) + * { + * QList queries = parser.getQueries(); + * qDebug() << "number of queries parsed:" << queries.size(); + * foreach (SqliteQueryPtr query, queries) + * { + * // do stuff with parsed queries + * // ... + * if (query.dynamicCast()) + * { + * qDebug() << "it's a select!"; + * } + * } + * } + * else + * { + * qDebug() << "Error while parsing:" << parser.getErrorString(); + * } + * @endcode + * + * There's also a convenient parse() method with template argument. + * + * There is a getNextTokenCandidates() to ask for all valid (according to syntax + * rules) token types to be used after given query string, + * + * Finally, there is a parseExpr() to parse just a SQLite expression + * (http://sqlite.org/lang_expr.html). + * + * Parser works basing on SQLite grammar defined in sqlite2.y and sqlite3.y files. + * Since there are 2 completly separate grammar definitions, there are 2 dialects + * that the parser works with. + * + * This is a high-level API to the Lemon Parser, the original SQLite parser. + */ +class API_EXPORT Parser +{ + public: + /** + * @brief Creates parser for given SQLite dialect. + * @param dialect SQLite dialect to use. Can be changed later with setDialect(). + */ + Parser(Dialect dialect); + + /** + * @brief Releases internal resources. + */ + virtual ~Parser(); + + /** + * @brief Enables or disables low-level debug messages for this parser. + * @param enabled true to enable, false to disable debug messages. + * + * Enabling this causes detailed debug messages from the Lemon parser + * to be printed. It is useful if you cannot understand why the parser + * thinks that the query is incorrect, etc. + */ + void setLemonDebug(bool enabled); + + /** + * @brief Changes dialect used by parser. + * @param dialect Dialect to use. + */ + void setDialect(Dialect dialect); + + /** + * @brief Parses given query string. + * @param sql SQL query string to parse. Can be multiple queries separated with semicolon. + * @param ignoreMinorErrors If true, then parser will ignore minor errors. Detailed descritpion below. + * @return true if the query was successfully parsed, or false if not. + * + * When the parser encounters syntax error, it stops and returns false. The AST objects (parsed queries) + * are partially filled with data - as much as it was possible till the error. Errors can be examined + * with getErrors() or getErrorString(). + * + * The \p ignoreMinorErrors allows to ignore minor syntax errors. The minor error is the error + * when for example there's a SELECT query, but no result column was typed yet. Normally this is incorrect + * query, cause SELECT statement requires at least 1 result column, but we can tell parser to ignore it. + * + * The usual case for minor error is when there's a SQLite expression missing at position, where it's expected, + * or when the expression is incomplete, like database.table. (no column name as the last part). + */ + bool parse(const QString& sql, bool ignoreMinorErrors = false); + + /** + * @brief Parses SQLite expression. + * @param sql SQLite expression. + * @return Parsed object, or null on failure. Parser doesn't own parsed object, you have to take care of deleting it. + * + * SQLite expression is any expression that you could type after "SELECT * FROM WHERE", etc. + * It's syntax is described at: http://sqlite.org/lang_expr.html + */ + SqliteExpr* parseExpr(const QString& sql); + + /** + * @brief Parses given query and returns it AST specialized object. + * @tparam T Type of AST object to parse into. + * @param query SQL query string to parse. + * @return Shared pointer to the parsed AST object, or null pointer if the query could not be parsed, + * or the parsed object was not of the requested type. + * + * This is a convenient method to parse string query, pick first parsed query from getQueries() + * and case it into desired AST object type. If this process fails at any point, the result returned will be + * a null pointer. + * + * Example: + * @code + * Parser parser(db->getDialect()); + * SqliteSelectPtr select = parser.parse(queryString); + * if (!select) + * { + * qCritical() << "Could not parse" << queryString << "to a SELECT statement, details:" << parser.getErrorString(); + * return; + * } + * // do stuff with the 'select' object + * // ... + * @endcode + */ + template + QSharedPointer parse(const QString& query) + { + if (!parse(query) || getQueries().size() == 0) + return QSharedPointer(); + + return getQueries().first().dynamicCast(); + } + + /** + * @brief Tests what are possible valid candidates for the next token. + * @param sql Part of the SQL query to check for the next token. + * @return List of token candidates. + * + * This method gets list of all token types from Lexer::getEveryTokenType() and tests which of them does the parser + * accept for the next token after the given query. + * + * You should treat the results of this method as a list of token types, rather than explicit tokens. + * Each token in the results represents a logical grammar entity. You should look at the Token::type and Token::value, + * while the Token::value is meaningful only for Token::KEYWORD, or Token::OPERATOR. For other token types, the value + * is just an example value (like for Token::INTEGER all numbers are valid candidates, not just one returned + * from this method). + */ + TokenList getNextTokenCandidates(const QString& sql); + + /** + * @brief Provides list of queries parsed recently by the parser. + * @return List of queries. + * + * On successful execution this list should contain at least 1 query, unless parsed query + * was a blank string - in that case this method will return list with no elements. + * + * In case of parsing error it's undefined how many elements will be in the list + * and also how much of the information will be filled in the queries - it depends on where the error appeared. + */ + const QList& getQueries(); + + /** + * @brief Provides list of errors that occurred during parsing. + * @return List of errors. + * + * Usually there's just one error, but there are cases when there might be more error on the list. + * That would be for example if you type "!" somewhere in the query where it should not be. + * Parser can deal with such errors and proceed. Such errors are later reported as failed parsing after all, + * but parser can continue and provide more data for AST objects (even they will be result of failed parsing process) + * and find other errors. In such cases, there can be 2, or even more errors on the list. + */ + const QList& getErrors(); + + /** + * @brief Provides error message from recent failed parsing process. + * @return Error message. + * + * This is convenient method to get first error getom getErrors() and return message from it. + */ + QString getErrorString(); + + /** + * @brief Provides list of tokens procudes during parsing process. + * @return List of tokens. + * + * Parser tokenizes query in order to parse it. It stores those tokens, so you can use them and you don't + * need to put query through the Lexer again (after Parser did it). + */ + TokenList getParsedTokens(); + + /** + * @brief Tells whether most recent parsing was successful. + * @return true if parsing was successful, or false otherwise. + * + * This method tells result for: parse(), parse(), getNextTokenCandidates() and parseExpr(). + */ + bool isSuccessful() const; + + /** + * @brief Clears parser state. + * + * Clears any parsed queries, stored tokens, errors, etc. + */ + void reset(); + + private: + + /** + * @brief Does the actual parsing job. + * @param sql Query to be parsed. + * @param lookForExpectedToken true if the parsing should be in "look for valid token candidates" mode, + * or false for regular mode. + * @return true on success, or false on failure. + * + * Both parse() and getNextTokenCandidates() call this method. + */ + bool parseInternal(const QString &sql, bool lookForExpectedToken); + + /** + * @brief Probes token types against the current parser state. + * @param pParser Pointer to Lemon parser. + * + * Probes all token types against current state of the parser. After each probe, the result is stored + * and the parser state is restored to as what it was before the probe. + * + * After all tokens were probed, we have the full information on what tokens are welcome + * at this parser state. This information is stored in the acceptedTokens member. + */ + void expectedTokenLookup(void *pParser); + + /** + * @brief Initializes Parser's internals. + * + * Creates internal Lexer and ParserContext. + */ + void init(); + + /** + * @brief Cleans up Parser's resources. + * + * Deletes internal Lexer and ParserContext. + */ + void cleanUp(); + + /** + * @brief Propagates dialect to all AST objects. + * + * This is called after successful parsing to set the adequate SQLite dialect + * in all AST objects. + */ + void fillSqliteDialect(); + + /** + * @brief Creates Lemon parser. + * @return Pointer to Lemon parser. + */ + void* parseAlloc(void *(*mallocProc)(size_t)); + + /** + * @brief Releases memory of the Lemon parser. + * @param p Pointer to Lemon parser. + */ + void parseFree(void *p, void (*freeProc)(void*)); + + /** + * @brief Invokes next step of Lemon parsing process. + * @param yyp Pointer to the Lemon parser. + * @param yymajor Lemon token ID (Token::lemonType) of the next token to be parsed. + * @param yyminor Next Token object to be parsed. + * @param parserContext Common context object for the parsing process. + * + * This method feeds Lemon parser with next token. This is the major input method + * for parsing the query. It's a bridge between the high-level Parser API + * and the low-level Lemon parser. + */ + void parse(void *yyp, int yymajor, TokenPtr yyminor, ParserContext* parserContext); + + /** + * @brief Enables low-level parser debug messages. + * @param stream Stream to write messages to. + * @param zPrefix Prefix for all messages. + */ + void parseTrace(FILE *stream, char *zPrefix); + + /** + * @brief Copies Lemon parser state. + * @param other Input parser state. + * @return Copied parser state. + */ + void* parseCopyParserState(void* other); + + /** + * @brief Restores Lemon parser state from saved copy. + * @param saved Saved copy of Lemon parser state. + * @param target Parser state to restore from saved copy. + */ + void parseRestoreParserState(void* saved, void* target); + + /** + * @brief Releases memory used for the Lemon parser state copy. + * @param other Lemon parser state to be freed. + */ + void parseFreeSavedState(void* other); + + /** + * @brief Adds meaningless token into Lemon's parser stack. + * @param other Lemon parser. + * @param token Token to be added. + * + * This method is used to add spaces and comments to the Lemon's stack. + */ + void parseAddToken(void* other, TokenPtr token); + + /** + * @brief Parser's dialect. + */ + Dialect dialect; + + /** + * @brief Flag indicating if the Lemon low-level debug messages are enabled. + */ + bool debugLemon = false; + + /** + * @brief Parser's internal Lexer. + */ + Lexer* lexer = nullptr; + + /** + * @brief Parser's internal context shared for the all Lemon parsing steps. + * + * Context is used as an output from Lemon parser. Lemon parser stores error details, token maps, + * and others in it. + * + * On the other side, Parser class puts configuration into the Context, so Lemon + * can use it. + */ + ParserContext* context = nullptr; + + /** + * @brief List of valid tokens collected by expectedTokenLookup(). + */ + TokenList acceptedTokens; +}; + +#endif // PARSER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.cpp new file mode 100644 index 0000000..039c9a5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.cpp @@ -0,0 +1,45 @@ +#include "parser_helper_stubs.h" +#include "ast/sqlitecreatetable.h" + +ParserStubAlias::ParserStubAlias(const QString &name, bool asKw) +{ + this->name = name; + this->asKw = asKw; +} + +ParserIndexedBy::ParserIndexedBy(const QString &name) +{ + indexedBy = name; +} + +ParserIndexedBy::ParserIndexedBy(bool notIndexed) +{ + this->notIndexedKw = notIndexed; +} + + +ParserStubInsertOrReplace::ParserStubInsertOrReplace(bool replace) +{ + this->replace = replace; +} + +ParserStubInsertOrReplace::ParserStubInsertOrReplace(bool replace, SqliteConflictAlgo orConflict) +{ + this->replace = replace; + this->orConflict = orConflict; +} + + +ParserStubExplain::ParserStubExplain(bool explain, bool queryPlan) +{ + this->explain = explain; + this->queryPlan = queryPlan; +} + + +ParserDeferSubClause::ParserDeferSubClause(SqliteDeferrable deferrable, SqliteInitially initially) +{ + this->initially = initially; + this->deferrable = deferrable; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.h b/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.h new file mode 100644 index 0000000..97a6393 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.h @@ -0,0 +1,118 @@ +#ifndef PARSER_HELPER_STUBS_H +#define PARSER_HELPER_STUBS_H + +#include "parsercontext.h" +#include "ast/sqlitebegintrans.h" +#include "ast/sqlitecreatetable.h" +#include "ast/sqliteconflictalgo.h" +#include "ast/sqliteselect.h" +#include "ast/sqliteindexedcolumn.h" +#include "ast/sqliteforeignkey.h" +#include "ast/sqliteorderby.h" + +#include +#include + +/** @file + * + * This file contains only structures and functions + * that are helpful in parsers generated by lemon, + * because lemon uses C unions, therefore only primitive + * types can be used as data type. + * (see %type declarations in *.y files). + */ + +/** + * @brief Stores 'dbnm' grammar rule. + */ +struct ParserFullName +{ + QString name1 = QString::null; + QString name2 = QString::null; +}; + +/** + * @brief Stores EXPLAIN and QUERY PLAN grammar rules. + */ +struct ParserStubExplain +{ + ParserStubExplain(bool explain, bool queryPlan); + + bool explain; + bool queryPlan; +}; + +/** + * @brief Stores "OR conflict" grammar rules. + */ +struct ParserStubInsertOrReplace +{ + explicit ParserStubInsertOrReplace(bool replace); + ParserStubInsertOrReplace(bool replace, SqliteConflictAlgo orConflict); + + bool replace; + SqliteConflictAlgo orConflict; +}; + +/** + * @brief Stores grammar rules for BEGIN/END/COMMIT/ROLLBACK additional parameters. + */ +struct ParserStubTransDetails +{ + QString name = QString::null; + SqliteBeginTrans::Type type = SqliteBeginTrans::Type::null; + bool transactionKw = false; + bool toKw = false; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; +}; + +typedef QList ParserCreateTableColumnList; +typedef QList ParserCreateTableConstraintList; +typedef QList ParserCreateTableColumnConstraintList; +typedef QList ParserFkConditionList; +typedef QList ParserExprList; +typedef QList ParserResultColumnList; +typedef QList ParserOtherSourceList; +typedef QList ParserStringList; +typedef QList ParserOrderByList; +typedef QList ParserQueryList; +typedef QPair ParserSetValue; +typedef QList ParserSetValueList; +typedef QList ParserIndexedColumnList; +typedef QList ParserExprNestedList; + +/** + * @brief Stores parameters for defferable foreign keys. + */ +struct ParserDeferSubClause +{ + ParserDeferSubClause(SqliteDeferrable deferrable, SqliteInitially initially); + + SqliteInitially initially; + SqliteDeferrable deferrable; +}; + +/** + * @brief Stores "AS aliasName" grammar rule. + */ +struct ParserStubAlias +{ + ParserStubAlias(const QString& name, bool asKw); + + QString name = QString::null; + bool asKw = false; +}; + +/** + * @brief Stores NOT INDEXED/INDEXED BY grammar rules. + */ +struct ParserIndexedBy +{ + explicit ParserIndexedBy(const QString& name); + explicit ParserIndexedBy(bool indexedBy); + + bool notIndexedKw = false; + QString indexedBy = QString::null; +}; + +#endif // PARSER_HELPER_STUBS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.cpp new file mode 100644 index 0000000..7394e75 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.cpp @@ -0,0 +1,184 @@ +#include "parsercontext.h" +#include "parsererror.h" +#include "lexer.h" +#include + +ParserContext::~ParserContext() +{ + cleanUp(); +} + +void ParserContext::addQuery(SqliteQuery *query) +{ + parsedQueries << SqliteQueryPtr(query); +} + +void ParserContext::error(TokenPtr token, const QString &text) +{ + if (token->start > -1 && token->end > -1) + errors << new ParserError(token, text); + else if (managedTokens.size() > 0) + errors << new ParserError(managedTokens.last()->start, managedTokens.last()->end + 1, text); + else + errors << new ParserError(text); + + successful = false; +} + +void ParserContext::error(Token* token, const QString& text) +{ + error(getTokenPtr(token), text); +} + +void ParserContext::error(const QString &text) +{ + errors << new ParserError(text); + successful = false; +} + +void ParserContext::minorErrorAfterLastToken(const QString &text) +{ + if (ignoreMinorErrors) + return; + + if (managedTokens.isEmpty()) + { + qCritical() << "Tried to report minor error after last token, but there's no tokens!"; + return; + } + + error(managedTokens.last(), text); +} + +void ParserContext::minorErrorBeforeNextToken(const QString &text) +{ + if (ignoreMinorErrors) + return; + + raiseErrorBeforeNextToken = true; + nextTokenError = text; +} + +void ParserContext::errorAfterLastToken(const QString& text) +{ + if (managedTokens.isEmpty()) + { + qCritical() << "Tried to report error after last token, but there's no tokens!"; + return; + } + + error(managedTokens.last(), text); +} + +void ParserContext::errorBeforeNextToken(const QString& text) +{ + raiseErrorBeforeNextToken = true; + nextTokenError = text; +} + +void ParserContext::errorAtToken(const QString& text, int pos) +{ + if (managedTokens.isEmpty()) + { + qCritical() << "Tried to report error at token" << pos << ", but there's no tokens!"; + return; + } + + int idx = managedTokens.size() - 1 + pos; + if (idx < 0 && idx >= managedTokens.size()) + { + qCritical() << "Tried to report error at token" << pos << ", calculated idx was out of range:" << idx + << "(manages tokens size:" << managedTokens.size() << ")."; + return; + } + + error(managedTokens[idx], text); +} + +void ParserContext::flushErrors() +{ + if (raiseErrorBeforeNextToken && !ignoreMinorErrors) + { + if (managedTokens.size() > 0) + error(managedTokens.last(), QObject::tr("Incomplete query.")); + else + error(QObject::tr("Incomplete query.")); + + nextTokenError = QString::null; + raiseErrorBeforeNextToken = false; + } +} + +TokenPtr ParserContext::getTokenPtr(Token* token) +{ + if (tokenPtrMap.contains(token)) + return tokenPtrMap[token]; + + TokenPtr tokenPtr = Lexer::getEveryTokenTypePtr(token); + if (!tokenPtr.isNull()) + return tokenPtr; + + qWarning() << "No TokenPtr for Token*. Token asked:" << token->toString(); + return TokenPtr(); +} + +TokenList ParserContext::getTokenPtrList(const QList& tokens) +{ + TokenList resList; + foreach (Token* token, tokens) + resList << getTokenPtr(token); + + return resList; +} + +void ParserContext::addManagedToken(TokenPtr token) +{ + managedTokens << token; + tokenPtrMap[token.data()] = token; + + if (raiseErrorBeforeNextToken) + { + error(token, nextTokenError); + nextTokenError = QString::null; + raiseErrorBeforeNextToken = false; + } +} + +bool ParserContext::isSuccessful() const +{ + return successful; +} + +const QList& ParserContext::getQueries() +{ + return parsedQueries; +} + +const QList &ParserContext::getErrors() +{ + return errors; +} + +void ParserContext::cleanUp() +{ + foreach (ParserError* err, errors) + delete err; + + parsedQueries.clear(); + errors.clear(); + managedTokens.clear(); + nextTokenError.clear(); + tokenPtrMap.clear(); + raiseErrorBeforeNextToken = false; + successful = true; +} + +bool ParserContext::isManagedToken(Token* token) +{ + return tokenPtrMap.contains(token); +} + +TokenList ParserContext::getManagedTokens() +{ + return managedTokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.h b/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.h new file mode 100644 index 0000000..d52c021 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.h @@ -0,0 +1,289 @@ +#ifndef PARSERCONTEXT_H +#define PARSERCONTEXT_H + +#include "ast/sqlitequery.h" +#include "parser.h" +#include "../dialect.h" + +#include +#include +#include + +class ParserError; + +/** + * @brief Parser context for SQL parsing process + * This class should not be used outside of @class Parser. + */ +class ParserContext +{ + friend class Parser; + + public: + /** + * @brief Releases all internal resources. + */ + virtual ~ParserContext(); + + /** + * @brief Adds parsed query to collection of parsed queries. + * @param query Parsed query (AST object). + * + * This is called by Lemon parser. + */ + void addQuery(SqliteQuery* query); + + /** + * @brief Stores error at given token with given message. + * @param token Token at which the error occurred. + * @param text Error message. + * + * This is called by Lemon parser. + */ + void error(TokenPtr token, const QString& text); + + /** + * @overload + */ + void error(Token* token, const QString& text); + + /** + * @brief Stores error with given message. + * @param text Error message. + * + * This method is used to report an error not related to specific token, + * like when Lemon's stack would get exceeded (which is very unlikely). + * + * This is called by Lemon parser. + * + * @overload + */ + void error(const QString& text); + + /** + * @brief Stores error with most recently parsed token and given message. + * @param text Error message. + * + * Lemon parser calls it when it found out that the error started at the token before. + * + * This is just a minor error, so it will be ognored if ignoreMinorErrors is set. + */ + void minorErrorAfterLastToken(const QString& text); + + /** + * @brief Marks next token to be parsed with given error message. + * @param text Error message. + * + * Lemon parser calls it when it knows that any next token will be an error. + * + * This is just a minor error, so it will be ognored if ignoreMinorErrors is set. + */ + void minorErrorBeforeNextToken(const QString& text); + + /** + * @brief Stores error message for most recently parsed token. + * @param text Error message. + * + * Lemon parser calls it when critical error occurred at the most recently parsed token. + */ + void errorAfterLastToken(const QString& text); + + /** + * @brief Stores error message for the next token to be parsed. + * @param text Error message. + * + * Lemon parser calls it when critical error is about to happen at any next token. + */ + void errorBeforeNextToken(const QString& text); + + /** + * @brief Reports parsing error at given token position. + * @param text Error message. + * @param pos Position relative to after the last token. -1 means the last token, -2 the token before it and so on. -1 is default. + * + * This method is only useful when we know exactly which token was problematic. If error relates to some already wrapped + * syntax rule, it may have many tokens and it's hard to tell which token should we blame, but sometimes it can be calculated. + * Anyway, the token with error is reported by the pos argument. If you don't pass it, it means the error is at last token. + * + * Lemon parser uses it for example when there's a statement "CREATE TABLE ... (...) WITHOUT ROWID". The SQLite grammar + * rule says, that the "ROWID" at the end is not necessarily the ROWID keyword, but it can be any word, + * but for now SQLite doesn't understand any other words at that position anyway and returns errors. + */ + void errorAtToken(const QString& text, int pos = -1); + + /** + * @brief Flushes pending errors. + * + * In case the errorBeforeNextToken() was called and no more tokens were feed to the context, then this method flushes + * pending error as the error for the last token consumed, but only if minor errors are not ignored. + * This happens for example for "SELECT " statement, where it's not correct, but it's a minor error, cause user + * might enter more contents afterwards. + */ + void flushErrors(); + + /** + * @brief Translates token pointer to it's shared pointer instance. + * @param token Token pointer to translate. + * @return QSharedPointer for the token, or null shared pointer in case of failure. + * + * This method works basing on internal collection of managed tokens. At each step of parsing, the internal lexer + * provides token (in form of shared pointer) and that token is then passed to the Lemon parser (as a pure C++ pointer, + * extracted from shared pointer). The very same token is stored in the internal collection of managed tokens (as a shared pointer). + * This method allows to get back to the shared pointer. + * + * This method is necessary to use shared pointers together with Lemon parser, which works on unions and won't be able to use + * shared pointers. + */ + TokenPtr getTokenPtr(Token* token); + + /** + * @brief Translates list of token pointers to their shared pointer instances. + * @param tokens Token pointers to translate. + * @return List of QSharedPointers. + * + * This method is just a convenience way to call getTokenPtr() for a list of pointers. + */ + TokenList getTokenPtrList(const QList& tokens); + + /** + * @brief Adds token to managed list. + * @param token Token to be added to managed tokens. + * Tokens managed by context are shared to the Parser, so the API allows to see all parsed tokens. + * Some tokens might be created outside of Lexer, so this is the central repository for all tokens to be shared. + */ + void addManagedToken(TokenPtr token); + + /** + * @brief Tests whether the token is in the collection of tokens managed by this context. + * @param token Token to test. + * @return true if the token is managed by this context, or false if not. + */ + bool isManagedToken(Token* token); + + /** + * @brief Provides complete list of tokens managed by this context. + * @return List of tokens. + */ + TokenList getManagedTokens(); + + /** + * @brief Tests whether there were any critical errors so far during parsing. + * @return true if there were no critical errors, or false otherwise. + */ + bool isSuccessful() const; + + /** + * @brief Provides access to list of queries parsed so far. + * @return List of parsed AST objects. + * + * If there was an error, then queries from the list might be incomplete, which means their data members + * may still be initialized with their default values. It depends on where the error appeared in the parsed query string. + */ + const QList& getQueries(); + + /** + * @brief Provides access to all errors occurred so far. + * @return List of errors. + */ + const QList& getErrors(); + + /** + * @brief Flag indicating if the Lemon parser should setup token collections. + * + * This setting allows to define whether the Lemon parser should setup token collections for parsed AST objects. + * In other words, it tells whether the SqliteStatement::tokens and SqliteStatement::tokensMap should be filled. + * + * Sometimes it might be worth to disable it to speed up parsig process, but by default it's enabled. + */ + bool setupTokens = true; + + /** + * @brief Flag inficating if the Lemon parser should exectute code for the grammar rules. + * + * This setting allows to define whether the Lemon parser should execute the code associated with rules. + * Disabling it will cause no AST objects to be produced, but it can be used to find out syntax errors. + * If you don't need AST objects (output from parsing), then you can turn this off to speed up Lemon parser. + * + * The Parser class for example turns it of when it probes for next valid token candidates. In that case + * no AST output objects are used, just information whether the next candidate is valid or not. + */ + bool executeRules = true; + + /** + * @brief Flag indicating if the Lemon parser should perform "fallback" logic. + * + * The "fallback" login in the Lemon parser is used when the input token is one of the keywords and it failed + * at that step. Then the "fallback" steps in and converts keyword token into the "ID" token, which represents + * a name of any object in the database (not necessarily existing one). Then the Lemon parser retries with + * that ID token and if that fails to fulfill the syntax rules too, then the error is reported. + * + * This is enabled by default, cause SQLite usually uses that too. It is for example disabled when looking + * for the next valid token candidate in Parser::getNextTokenCandidates(), cause for that case we need + * very strict token matching against the syntax. + */ + bool doFallbacks = true; + + /** + * @brief Flag indicating if minor errors should be ignored by the Lemon parser. + * + * See description of Parser::parse() for details. + */ + bool ignoreMinorErrors = false; + + /** + * @brief Dialect used for the parsing. + * + * This is used by the Lemon parser in various situations, like for example when it strips the object name + * from it's wrapping characters ([], "", ``) - that depends on the dialect. + */ + Dialect dialect; + + private: + /** + * @brief Clears all internal containers and deletes error objects. + */ + void cleanUp(); + + /** + * @brief List of parsed AST objects. + */ + QList parsedQueries; + + /** + * @brief Tokens managed by this context. + */ + TokenList managedTokens; + + /** + * @brief Mapping from token pointer to it's shared pointer instance. + */ + QHash tokenPtrMap; + + /** + * @brief Flag indicating successful or failure parsing. + * + * Changed to false when the error was reported. + */ + bool successful = true; + + /** + * @brief List of errors reported by Lemon. + */ + QList errors; + + /** + * @brief Flag indicating that the next token should raise an error. + * + * This is set by errorBeforeNextToken() and minorErrorBeforeNextToken(). + */ + bool raiseErrorBeforeNextToken = false; + + /** + * @brief Error to be used for the error at next token. + * + * Defined by errorBeforeNextToken() and minorErrorBeforeNextToken(). + */ + QString nextTokenError; +}; + +#endif // PARSERCONTEXT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.cpp new file mode 100644 index 0000000..22fc531 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.cpp @@ -0,0 +1,44 @@ +#include "parsererror.h" +#include "token.h" + +ParserError::ParserError(TokenPtr token, const QString &text) +{ + if (token) + { + start = token->start; + end = token->end; + } + message = text; +} + +ParserError::ParserError(qint64 start, qint64 end, const QString& text) : + message(text), + start(start), + end(end) +{ +} + +ParserError::ParserError(const QString &text) +{ + message = text; +} + +QString &ParserError::getMessage() +{ + return message; +} + +qint64 ParserError::getFrom() +{ + return start; +} + +qint64 ParserError::getTo() +{ + return end; +} + +QString ParserError::toString() +{ + return QString("%1: %2").arg(start).arg(message); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.h b/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.h new file mode 100644 index 0000000..673f030 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.h @@ -0,0 +1,80 @@ +#ifndef PARSERERROR_H +#define PARSERERROR_H + +#include "coreSQLiteStudio_global.h" +#include "parser/token.h" +#include + +/** + * @brief Class representing error during SQL parsing. + * + * It provides error message and position at which the error occurred. + */ +class API_EXPORT ParserError +{ + public: + /** + * @brief Creates error for given token and message. + * @param token Token that the error occurred at. + * @param text Error message. + */ + ParserError(TokenPtr token, const QString& text); + + /** + * @brief Creates error with given range and message. + * @param start Position where the error starts. + * @param end Position where the error ends. + * @param text Error message. + */ + ParserError(qint64 start, qint64 end, const QString& text); + + /** + * @brief Creates global error with given message. + * @param text Error message. + * + * Global errors are not related to any token or position. + */ + explicit ParserError(const QString& text); + + /** + * @brief Provides error message. + * @return Error message. + */ + QString& getMessage(); + + /** + * @brief Provides start position of the error. + * @return Character position, or -1 if the error is not related to any position (global error). + */ + qint64 getFrom(); + + /** + * @brief Provides end position of the error. + * @return Character position, or -1 if the error is not related to any position (global error). + */ + qint64 getTo(); + + /** + * @brief Serializes error to readable string. + * @return Start position and error message in form: "position: message". + */ + QString toString(); + + private: + /** + * @brief Error message. + */ + QString message = QString::null; + + /** + * @brief Error start position. + */ + qint64 start = -1; + + /** + * @brief Error end position. + */ + qint64 end = -1; +}; + +#endif // PARSERERROR_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh b/SQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh new file mode 100755 index 0000000..3df47e1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +#lemon -l -q -s sqlite3_parse.y +lemon -l -q sqlite3_parse.y +mv sqlite3_parse.c sqlite3_parse.cpp + +lemon -l -q sqlite2_parse.y +mv sqlite2_parse.c sqlite2_parse.cpp diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.cpp new file mode 100644 index 0000000..7fc9edc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.cpp @@ -0,0 +1,4650 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +** +** This version of "lempar.c" is modified, slightly, for use by SQLite. +** The only modifications are the addition of a couple of NEVER() +** macros to disable tests that are needed in the case of a general +** LALR(1) grammar but which are always false in the +** specific grammar used by SQLite. +*/ +/* First off, code is included that follows the "include" declaration +** in the input grammar file. */ +#include + +#include "token.h" +#include "parsercontext.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser_helper_stubs.h" +#include "common/utils_sql.h" +#include +#include + +#define assert(X) Q_ASSERT(X) +#define UNUSED_PARAMETER(X) (void)(X) +#define DONT_INHERIT_TOKENS(X) noTokenInheritanceFields << X +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** sqlite2_parseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is sqlite2_parseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** sqlite2_parseARG_SDECL A static variable declaration for the %extra_argument +** sqlite2_parseARG_PDECL A parameter declaration for the %extra_argument +** sqlite2_parseARG_STORE Code to store %extra_argument into yypParser +** sqlite2_parseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +#define YYCODETYPE unsigned char +#define YYNOCODE 241 +#define YYACTIONTYPE unsigned short int +#define sqlite2_parseTOKENTYPE Token* +typedef union { + int yyinit; + sqlite2_parseTOKENTYPE yy0; + ParserCreateTableConstraintList* yy13; + SqliteSelect::Core::JoinSource* yy31; + ParserStubAlias* yy40; + SqliteExpr::LikeOp* yy41; + ParserCreateTableColumnList* yy42; + SqliteColumnType* yy57; + ParserIndexedColumnList* yy63; + QVariant* yy69; + SqliteCreateTrigger::Scope* yy83; + ParserStubExplain* yy91; + ParserFullName* yy120; + SqliteSelect::Core::SingleSource* yy121; + ParserOtherSourceList* yy131; + SqliteCreateTable::Column* yy147; + SqliteSelect::Core* yy150; + SqliteCreateTrigger::Event* yy151; + SqliteSelect* yy153; + SqliteForeignKey::Condition* yy187; + SqliteExpr* yy192; + ParserSetValueList* yy201; + SqliteQuery* yy203; + ParserStringList* yy207; + ParserResultColumnList* yy213; + SqliteSelect::Core::JoinOp* yy221; + int* yy226; + ParserExprList* yy231; + ParserOrderByList* yy243; + ParserFkConditionList* yy264; + ParserQueryList* yy270; + bool* yy291; + SqliteCreateTable::Column::Constraint* yy304; + SqliteInitially* yy312; + QString* yy319; + SqliteLimit* yy324; + ParserDeferSubClause* yy329; + ParserStubInsertOrReplace* yy344; + ParserCreateTableColumnConstraintList* yy371; + SqliteCreateTrigger::Time* yy372; + SqliteSelect::CompoundOperator* yy382; + SqliteSortOrder* yy389; + ParserStubTransDetails* yy404; + SqliteCreateTable::Constraint* yy406; + SqliteConflictAlgo* yy418; + SqliteForeignKey::Condition::Reaction* yy424; + SqliteIndexedColumn* yy428; + SqliteSelect::Core::JoinConstraint* yy455; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define sqlite2_parseARG_SDECL ParserContext* parserContext; +#define sqlite2_parseARG_PDECL ,ParserContext* parserContext +#define sqlite2_parseARG_FETCH ParserContext* parserContext = yypParser->parserContext +#define sqlite2_parseARG_STORE yypParser->parserContext = parserContext +#define YYNSTATE 584 +#define YYNRULE 352 +#define YYFALLBACK 1 +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +#define GET_CONTEXT yyParser* yypParser = pParser; sqlite2_parseARG_FETCH + +/* The yyzerominor constant is used to initialize instances of +** YYMINORTYPE objects to zero. */ +static const YYMINORTYPE yyzerominor = { 0 }; + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +#define YY_ACTTAB_COUNT (1697) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 338, 191, 186, 242, 476, 511, 576, 193, 332, 16, + /* 10 */ 511, 384, 189, 322, 239, 519, 518, 570, 337, 450, + /* 20 */ 15, 851, 125, 58, 575, 179, 851, 574, 63, 142, + /* 30 */ 401, 581, 328, 27, 84, 569, 114, 322, 573, 519, + /* 40 */ 518, 851, 851, 36, 851, 851, 851, 851, 851, 851, + /* 50 */ 851, 851, 851, 851, 851, 851, 851, 851, 851, 33, + /* 60 */ 34, 851, 851, 851, 851, 320, 379, 35, 240, 238, + /* 70 */ 121, 556, 291, 251, 57, 7, 217, 577, 265, 264, + /* 80 */ 523, 179, 555, 336, 335, 338, 569, 549, 548, 550, + /* 90 */ 271, 569, 10, 724, 199, 297, 203, 489, 459, 332, + /* 100 */ 568, 567, 451, 253, 158, 523, 449, 444, 443, 337, + /* 110 */ 187, 870, 266, 187, 580, 517, 168, 328, 505, 343, + /* 120 */ 142, 235, 490, 108, 101, 489, 523, 164, 36, 531, + /* 130 */ 187, 13, 523, 252, 234, 547, 236, 119, 340, 232, + /* 140 */ 339, 120, 216, 155, 33, 34, 481, 547, 322, 215, + /* 150 */ 519, 518, 35, 714, 456, 477, 320, 367, 547, 478, + /* 160 */ 7, 714, 400, 321, 251, 523, 714, 563, 336, 335, + /* 170 */ 500, 553, 549, 548, 550, 188, 465, 500, 401, 466, + /* 180 */ 366, 365, 552, 364, 293, 435, 40, 40, 40, 39, + /* 190 */ 523, 562, 60, 255, 714, 569, 714, 714, 852, 570, + /* 200 */ 271, 714, 502, 852, 500, 714, 570, 714, 714, 714, + /* 210 */ 714, 523, 569, 178, 531, 422, 13, 523, 45, 46, + /* 220 */ 330, 43, 43, 530, 530, 223, 852, 852, 44, 44, + /* 230 */ 44, 44, 42, 42, 42, 42, 41, 41, 40, 40, + /* 240 */ 40, 39, 199, 297, 203, 55, 236, 92, 340, 232, + /* 250 */ 339, 120, 216, 500, 106, 570, 268, 19, 187, 215, + /* 260 */ 500, 105, 553, 42, 42, 42, 42, 41, 41, 40, + /* 270 */ 40, 40, 39, 552, 41, 41, 40, 40, 40, 39, + /* 280 */ 852, 3, 568, 567, 187, 852, 512, 500, 219, 568, + /* 290 */ 567, 17, 496, 156, 322, 345, 519, 518, 569, 292, + /* 300 */ 45, 46, 330, 43, 43, 530, 530, 223, 852, 852, + /* 310 */ 44, 44, 44, 44, 42, 42, 42, 42, 41, 41, + /* 320 */ 40, 40, 40, 39, 547, 533, 852, 100, 308, 306, + /* 330 */ 305, 852, 448, 447, 418, 418, 316, 245, 568, 567, + /* 340 */ 304, 937, 122, 344, 1, 582, 45, 46, 330, 43, + /* 350 */ 43, 530, 530, 223, 852, 852, 44, 44, 44, 44, + /* 360 */ 42, 42, 42, 42, 41, 41, 40, 40, 40, 39, + /* 370 */ 338, 442, 45, 46, 330, 43, 43, 530, 530, 223, + /* 380 */ 524, 569, 44, 44, 44, 44, 42, 42, 42, 42, + /* 390 */ 41, 41, 40, 40, 40, 39, 5, 9, 524, 781, + /* 400 */ 220, 324, 328, 167, 45, 46, 330, 43, 43, 530, + /* 410 */ 530, 223, 781, 36, 44, 44, 44, 44, 42, 42, + /* 420 */ 42, 42, 41, 41, 40, 40, 40, 39, 8, 33, + /* 430 */ 34, 274, 387, 435, 547, 388, 237, 35, 774, 421, + /* 440 */ 60, 14, 219, 569, 250, 7, 774, 437, 441, 169, + /* 450 */ 523, 524, 569, 336, 335, 285, 781, 549, 548, 550, + /* 460 */ 44, 44, 44, 44, 42, 42, 42, 42, 41, 41, + /* 470 */ 40, 40, 40, 39, 332, 523, 332, 425, 470, 774, + /* 480 */ 560, 774, 774, 850, 337, 426, 337, 455, 850, 613, + /* 490 */ 774, 181, 774, 774, 774, 142, 523, 142, 31, 531, + /* 500 */ 614, 13, 523, 850, 850, 850, 850, 850, 850, 850, + /* 510 */ 850, 850, 850, 850, 850, 850, 850, 850, 850, 850, + /* 520 */ 850, 850, 850, 850, 850, 850, 850, 852, 500, 460, + /* 530 */ 357, 320, 852, 318, 75, 570, 401, 570, 311, 251, + /* 540 */ 569, 251, 446, 445, 570, 358, 359, 45, 46, 330, + /* 550 */ 43, 43, 530, 530, 223, 852, 852, 44, 44, 44, + /* 560 */ 44, 42, 42, 42, 42, 41, 41, 40, 40, 40, + /* 570 */ 39, 338, 45, 46, 330, 43, 43, 530, 530, 223, + /* 580 */ 615, 570, 44, 44, 44, 44, 42, 42, 42, 42, + /* 590 */ 41, 41, 40, 40, 40, 39, 185, 184, 497, 782, + /* 600 */ 199, 297, 203, 328, 360, 538, 96, 488, 97, 570, + /* 610 */ 103, 500, 782, 436, 36, 570, 187, 225, 568, 567, + /* 620 */ 568, 567, 332, 354, 540, 539, 294, 568, 567, 570, + /* 630 */ 33, 34, 337, 356, 482, 355, 569, 85, 35, 759, + /* 640 */ 532, 111, 70, 78, 487, 483, 7, 759, 90, 494, + /* 650 */ 508, 523, 493, 160, 336, 335, 782, 528, 549, 548, + /* 660 */ 550, 532, 440, 434, 568, 567, 118, 54, 332, 919, + /* 670 */ 214, 100, 308, 306, 305, 75, 523, 333, 337, 571, + /* 680 */ 759, 529, 759, 759, 304, 48, 177, 522, 32, 142, + /* 690 */ 30, 759, 568, 567, 759, 759, 159, 523, 568, 567, + /* 700 */ 531, 464, 13, 523, 45, 46, 330, 43, 43, 530, + /* 710 */ 530, 223, 568, 567, 44, 44, 44, 44, 42, 42, + /* 720 */ 42, 42, 41, 41, 40, 40, 40, 39, 463, 570, + /* 730 */ 498, 919, 570, 323, 514, 570, 222, 45, 46, 330, + /* 740 */ 43, 43, 530, 530, 223, 393, 392, 44, 44, 44, + /* 750 */ 44, 42, 42, 42, 42, 41, 41, 40, 40, 40, + /* 760 */ 39, 45, 46, 330, 43, 43, 530, 530, 223, 540, + /* 770 */ 539, 44, 44, 44, 44, 42, 42, 42, 42, 41, + /* 780 */ 41, 40, 40, 40, 39, 583, 1, 45, 46, 330, + /* 790 */ 43, 43, 530, 530, 223, 267, 475, 44, 44, 44, + /* 800 */ 44, 42, 42, 42, 42, 41, 41, 40, 40, 40, + /* 810 */ 39, 434, 568, 567, 420, 568, 567, 163, 568, 567, + /* 820 */ 570, 110, 218, 45, 46, 330, 43, 43, 530, 530, + /* 830 */ 223, 28, 468, 44, 44, 44, 44, 42, 42, 42, + /* 840 */ 42, 41, 41, 40, 40, 40, 39, 570, 547, 45, + /* 850 */ 46, 330, 43, 43, 530, 530, 223, 570, 212, 44, + /* 860 */ 44, 44, 44, 42, 42, 42, 42, 41, 41, 40, + /* 870 */ 40, 40, 39, 46, 330, 43, 43, 530, 530, 223, + /* 880 */ 527, 526, 44, 44, 44, 44, 42, 42, 42, 42, + /* 890 */ 41, 41, 40, 40, 40, 39, 338, 330, 43, 43, + /* 900 */ 530, 530, 223, 568, 567, 44, 44, 44, 44, 42, + /* 910 */ 42, 42, 42, 41, 41, 40, 40, 40, 39, 570, + /* 920 */ 525, 570, 64, 859, 88, 424, 198, 391, 328, 520, + /* 930 */ 568, 567, 357, 570, 516, 164, 569, 380, 12, 36, + /* 940 */ 568, 567, 569, 164, 502, 25, 570, 358, 275, 172, + /* 950 */ 171, 170, 391, 256, 569, 33, 34, 310, 66, 389, + /* 960 */ 390, 287, 76, 35, 104, 395, 547, 484, 561, 79, + /* 970 */ 452, 7, 862, 395, 547, 455, 523, 338, 80, 336, + /* 980 */ 335, 213, 480, 549, 548, 550, 75, 312, 396, 398, + /* 990 */ 570, 397, 293, 479, 570, 380, 396, 398, 259, 397, + /* 1000 */ 293, 523, 568, 567, 568, 567, 276, 538, 10, 328, + /* 1010 */ 4, 289, 474, 490, 570, 473, 568, 567, 23, 290, + /* 1020 */ 36, 65, 523, 431, 284, 531, 532, 13, 523, 568, + /* 1030 */ 567, 497, 570, 584, 439, 332, 33, 34, 490, 317, + /* 1040 */ 162, 489, 535, 570, 35, 337, 570, 532, 570, 179, + /* 1050 */ 307, 53, 7, 471, 52, 345, 139, 523, 338, 569, + /* 1060 */ 336, 335, 179, 302, 549, 548, 550, 495, 75, 303, + /* 1070 */ 413, 547, 569, 568, 567, 570, 254, 568, 567, 325, + /* 1080 */ 262, 209, 523, 272, 570, 18, 569, 361, 160, 570, + /* 1090 */ 328, 569, 515, 261, 407, 413, 51, 568, 567, 570, + /* 1100 */ 522, 36, 569, 523, 192, 582, 531, 10, 13, 523, + /* 1110 */ 99, 338, 489, 730, 569, 568, 567, 33, 34, 407, + /* 1120 */ 503, 500, 456, 403, 570, 35, 568, 567, 376, 568, + /* 1130 */ 567, 568, 567, 7, 211, 456, 570, 490, 523, 570, + /* 1140 */ 489, 336, 335, 328, 570, 549, 548, 550, 403, 497, + /* 1150 */ 730, 730, 404, 376, 36, 371, 500, 108, 568, 567, + /* 1160 */ 124, 101, 234, 523, 486, 279, 82, 568, 567, 2, + /* 1170 */ 33, 34, 568, 567, 497, 569, 197, 301, 35, 368, + /* 1180 */ 69, 75, 568, 567, 523, 492, 7, 531, 666, 13, + /* 1190 */ 523, 523, 338, 423, 336, 335, 417, 73, 549, 548, + /* 1200 */ 550, 350, 485, 570, 568, 567, 570, 568, 567, 22, + /* 1210 */ 491, 570, 21, 67, 371, 179, 523, 6, 258, 568, + /* 1220 */ 567, 219, 568, 567, 328, 569, 405, 568, 567, 570, + /* 1230 */ 255, 569, 399, 106, 394, 36, 215, 523, 71, 500, + /* 1240 */ 531, 368, 13, 523, 416, 860, 260, 386, 570, 415, + /* 1250 */ 338, 33, 34, 269, 544, 543, 205, 570, 204, 35, + /* 1260 */ 108, 350, 409, 81, 570, 385, 569, 7, 502, 507, + /* 1270 */ 245, 496, 523, 182, 50, 336, 335, 570, 569, 549, + /* 1280 */ 548, 550, 328, 160, 338, 281, 568, 567, 456, 568, + /* 1290 */ 567, 570, 381, 36, 568, 567, 115, 523, 278, 277, + /* 1300 */ 570, 351, 570, 363, 353, 187, 352, 49, 411, 33, + /* 1310 */ 34, 406, 568, 567, 538, 160, 328, 35, 523, 174, + /* 1320 */ 157, 531, 286, 13, 523, 7, 862, 36, 185, 184, + /* 1330 */ 523, 568, 567, 336, 335, 179, 362, 549, 548, 550, + /* 1340 */ 568, 567, 180, 33, 34, 569, 569, 568, 567, 383, + /* 1350 */ 74, 35, 202, 377, 296, 523, 540, 539, 241, 7, + /* 1360 */ 568, 567, 569, 579, 523, 374, 257, 336, 335, 342, + /* 1370 */ 112, 549, 548, 550, 568, 567, 523, 346, 201, 531, + /* 1380 */ 200, 13, 523, 568, 567, 568, 567, 569, 569, 523, + /* 1390 */ 542, 544, 543, 536, 544, 543, 566, 332, 263, 544, + /* 1400 */ 543, 332, 565, 373, 309, 544, 543, 337, 456, 564, + /* 1410 */ 523, 337, 341, 531, 332, 13, 523, 501, 77, 370, + /* 1420 */ 11, 559, 146, 196, 337, 378, 457, 332, 408, 558, + /* 1430 */ 557, 270, 332, 569, 332, 144, 554, 337, 187, 332, + /* 1440 */ 551, 546, 337, 17, 337, 47, 332, 332, 151, 337, + /* 1450 */ 227, 538, 228, 150, 538, 152, 337, 337, 229, 538, + /* 1460 */ 161, 24, 522, 226, 319, 538, 522, 224, 249, 334, + /* 1470 */ 59, 545, 220, 332, 332, 332, 39, 521, 37, 522, + /* 1480 */ 332, 332, 183, 337, 337, 337, 29, 109, 332, 510, + /* 1490 */ 337, 337, 522, 572, 143, 149, 145, 522, 337, 522, + /* 1500 */ 332, 248, 247, 569, 522, 332, 332, 570, 332, 246, + /* 1510 */ 337, 522, 522, 332, 332, 337, 337, 513, 337, 107, + /* 1520 */ 83, 141, 332, 337, 337, 472, 133, 132, 454, 140, + /* 1530 */ 56, 332, 337, 244, 131, 148, 453, 430, 522, 522, + /* 1540 */ 522, 337, 429, 147, 428, 522, 522, 332, 332, 534, + /* 1550 */ 326, 332, 130, 522, 332, 243, 332, 337, 337, 569, + /* 1560 */ 569, 337, 427, 419, 337, 522, 337, 98, 129, 127, + /* 1570 */ 522, 522, 135, 522, 332, 134, 332, 136, 522, 522, + /* 1580 */ 195, 332, 372, 314, 337, 207, 337, 522, 95, 288, + /* 1590 */ 569, 337, 94, 337, 206, 138, 522, 137, 298, 194, + /* 1600 */ 20, 369, 128, 414, 61, 504, 68, 153, 499, 569, + /* 1610 */ 102, 93, 522, 522, 315, 569, 522, 469, 569, 522, + /* 1620 */ 569, 522, 432, 210, 569, 300, 91, 569, 123, 72, + /* 1630 */ 166, 402, 569, 569, 117, 569, 89, 295, 283, 522, + /* 1640 */ 569, 522, 375, 382, 280, 331, 522, 569, 522, 221, + /* 1650 */ 208, 176, 569, 87, 86, 569, 347, 175, 116, 569, + /* 1660 */ 569, 569, 349, 173, 113, 126, 233, 541, 230, 154, + /* 1670 */ 537, 329, 509, 506, 458, 410, 273, 299, 470, 282, + /* 1680 */ 190, 348, 467, 462, 461, 438, 38, 165, 938, 62, + /* 1690 */ 938, 327, 231, 578, 433, 938, 412, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 4, 8, 195, 10, 80, 25, 5, 14, 156, 202, + /* 10 */ 30, 41, 19, 113, 21, 115, 116, 4, 166, 4, + /* 20 */ 79, 25, 81, 34, 23, 156, 30, 26, 35, 177, + /* 30 */ 37, 72, 36, 53, 54, 166, 43, 113, 37, 115, + /* 40 */ 116, 45, 46, 47, 48, 49, 50, 51, 52, 53, + /* 50 */ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + /* 60 */ 64, 65, 66, 67, 68, 213, 96, 71, 75, 76, + /* 70 */ 77, 16, 220, 221, 85, 79, 156, 76, 63, 64, + /* 80 */ 84, 156, 27, 87, 88, 4, 166, 91, 92, 93, + /* 90 */ 47, 166, 79, 80, 101, 102, 103, 84, 229, 156, + /* 100 */ 87, 88, 87, 110, 161, 109, 91, 92, 93, 166, + /* 110 */ 117, 141, 187, 117, 148, 80, 161, 36, 80, 153, + /* 120 */ 177, 93, 109, 85, 31, 112, 130, 161, 47, 133, + /* 130 */ 117, 135, 136, 90, 106, 192, 93, 94, 95, 96, + /* 140 */ 97, 98, 99, 13, 63, 64, 65, 192, 113, 106, + /* 150 */ 115, 116, 71, 72, 229, 114, 213, 191, 192, 118, + /* 160 */ 79, 80, 21, 220, 221, 84, 85, 80, 87, 88, + /* 170 */ 4, 7, 91, 92, 93, 32, 90, 4, 37, 93, + /* 180 */ 214, 215, 18, 217, 218, 156, 65, 66, 67, 68, + /* 190 */ 109, 162, 163, 100, 113, 166, 115, 116, 25, 4, + /* 200 */ 47, 120, 156, 30, 4, 124, 4, 126, 127, 128, + /* 210 */ 129, 130, 166, 184, 133, 186, 135, 136, 45, 46, + /* 220 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 230 */ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + /* 240 */ 67, 68, 101, 102, 103, 79, 93, 85, 95, 96, + /* 250 */ 97, 98, 99, 87, 81, 4, 210, 79, 117, 106, + /* 260 */ 87, 80, 7, 59, 60, 61, 62, 63, 64, 65, + /* 270 */ 66, 67, 68, 18, 63, 64, 65, 66, 67, 68, + /* 280 */ 25, 79, 87, 88, 117, 30, 84, 87, 156, 87, + /* 290 */ 88, 129, 119, 161, 113, 22, 115, 116, 166, 132, + /* 300 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + /* 310 */ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + /* 320 */ 65, 66, 67, 68, 192, 130, 25, 94, 95, 96, + /* 330 */ 97, 30, 91, 92, 83, 84, 204, 205, 87, 88, + /* 340 */ 107, 149, 150, 151, 152, 72, 45, 46, 47, 48, + /* 350 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + /* 360 */ 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, + /* 370 */ 4, 156, 45, 46, 47, 48, 49, 50, 51, 52, + /* 380 */ 5, 166, 55, 56, 57, 58, 59, 60, 61, 62, + /* 390 */ 63, 64, 65, 66, 67, 68, 79, 48, 23, 72, + /* 400 */ 99, 26, 36, 161, 45, 46, 47, 48, 49, 50, + /* 410 */ 51, 52, 85, 47, 55, 56, 57, 58, 59, 60, + /* 420 */ 61, 62, 63, 64, 65, 66, 67, 68, 79, 63, + /* 430 */ 64, 82, 6, 156, 192, 9, 159, 71, 72, 162, + /* 440 */ 163, 125, 156, 166, 164, 79, 80, 167, 168, 169, + /* 450 */ 84, 76, 166, 87, 88, 29, 129, 91, 92, 93, + /* 460 */ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + /* 470 */ 65, 66, 67, 68, 156, 109, 156, 180, 181, 113, + /* 480 */ 173, 115, 116, 25, 166, 188, 166, 180, 30, 82, + /* 490 */ 124, 205, 126, 127, 128, 177, 130, 177, 139, 133, + /* 500 */ 82, 135, 136, 45, 46, 47, 48, 49, 50, 51, + /* 510 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + /* 520 */ 62, 63, 64, 65, 66, 67, 68, 25, 4, 80, + /* 530 */ 156, 213, 30, 213, 85, 4, 37, 4, 220, 221, + /* 540 */ 166, 221, 91, 92, 4, 171, 172, 45, 46, 47, + /* 550 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 560 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + /* 570 */ 68, 4, 45, 46, 47, 48, 49, 50, 51, 52, + /* 580 */ 82, 4, 55, 56, 57, 58, 59, 60, 61, 62, + /* 590 */ 63, 64, 65, 66, 67, 68, 63, 64, 167, 72, + /* 600 */ 101, 102, 103, 36, 230, 231, 42, 85, 44, 4, + /* 610 */ 88, 87, 85, 89, 47, 4, 117, 85, 87, 88, + /* 620 */ 87, 88, 156, 90, 91, 92, 156, 87, 88, 4, + /* 630 */ 63, 64, 166, 100, 203, 102, 166, 82, 71, 72, + /* 640 */ 109, 82, 78, 177, 122, 123, 79, 80, 82, 109, + /* 650 */ 85, 84, 112, 222, 87, 88, 129, 21, 91, 92, + /* 660 */ 93, 130, 80, 86, 87, 88, 100, 85, 156, 10, + /* 670 */ 80, 94, 95, 96, 97, 85, 109, 211, 166, 74, + /* 680 */ 113, 93, 115, 116, 107, 120, 216, 221, 138, 177, + /* 690 */ 140, 124, 87, 88, 127, 128, 79, 130, 87, 88, + /* 700 */ 133, 11, 135, 136, 45, 46, 47, 48, 49, 50, + /* 710 */ 51, 52, 87, 88, 55, 56, 57, 58, 59, 60, + /* 720 */ 61, 62, 63, 64, 65, 66, 67, 68, 38, 4, + /* 730 */ 119, 72, 4, 221, 109, 4, 224, 45, 46, 47, + /* 740 */ 48, 49, 50, 51, 52, 39, 40, 55, 56, 57, + /* 750 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + /* 760 */ 68, 45, 46, 47, 48, 49, 50, 51, 52, 91, + /* 770 */ 92, 55, 56, 57, 58, 59, 60, 61, 62, 63, + /* 780 */ 64, 65, 66, 67, 68, 151, 152, 45, 46, 47, + /* 790 */ 48, 49, 50, 51, 52, 105, 80, 55, 56, 57, + /* 800 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + /* 810 */ 68, 86, 87, 88, 83, 87, 88, 161, 87, 88, + /* 820 */ 4, 85, 80, 45, 46, 47, 48, 49, 50, 51, + /* 830 */ 52, 139, 104, 55, 56, 57, 58, 59, 60, 61, + /* 840 */ 62, 63, 64, 65, 66, 67, 68, 4, 192, 45, + /* 850 */ 46, 47, 48, 49, 50, 51, 52, 4, 80, 55, + /* 860 */ 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + /* 870 */ 66, 67, 68, 46, 47, 48, 49, 50, 51, 52, + /* 880 */ 80, 80, 55, 56, 57, 58, 59, 60, 61, 62, + /* 890 */ 63, 64, 65, 66, 67, 68, 4, 47, 48, 49, + /* 900 */ 50, 51, 52, 87, 88, 55, 56, 57, 58, 59, + /* 910 */ 60, 61, 62, 63, 64, 65, 66, 67, 68, 4, + /* 920 */ 80, 4, 42, 138, 44, 109, 156, 84, 36, 114, + /* 930 */ 87, 88, 156, 4, 80, 161, 166, 84, 125, 47, + /* 940 */ 87, 88, 166, 161, 156, 85, 4, 171, 172, 101, + /* 950 */ 102, 103, 109, 157, 166, 63, 64, 65, 78, 101, + /* 960 */ 102, 103, 79, 71, 80, 191, 192, 122, 173, 174, + /* 970 */ 175, 79, 80, 191, 192, 180, 84, 4, 82, 87, + /* 980 */ 88, 80, 65, 91, 92, 93, 85, 82, 214, 215, + /* 990 */ 4, 217, 218, 65, 4, 142, 214, 215, 210, 217, + /* 1000 */ 218, 109, 87, 88, 87, 88, 230, 231, 79, 36, + /* 1010 */ 236, 237, 80, 84, 4, 80, 87, 88, 100, 237, + /* 1020 */ 47, 141, 130, 108, 228, 133, 109, 135, 136, 87, + /* 1030 */ 88, 167, 4, 0, 80, 156, 63, 64, 109, 121, + /* 1040 */ 161, 112, 176, 4, 71, 166, 4, 130, 4, 156, + /* 1050 */ 32, 79, 79, 80, 79, 22, 177, 84, 4, 166, + /* 1060 */ 87, 88, 156, 80, 91, 92, 93, 203, 85, 32, + /* 1070 */ 84, 192, 166, 87, 88, 4, 156, 87, 88, 137, + /* 1080 */ 187, 156, 109, 158, 4, 79, 166, 167, 222, 4, + /* 1090 */ 36, 166, 156, 187, 84, 109, 79, 87, 88, 4, + /* 1100 */ 221, 47, 166, 130, 156, 72, 133, 79, 135, 136, + /* 1110 */ 98, 4, 84, 85, 166, 87, 88, 63, 64, 109, + /* 1120 */ 130, 4, 229, 84, 4, 71, 87, 88, 84, 87, + /* 1130 */ 88, 87, 88, 79, 80, 229, 4, 109, 84, 4, + /* 1140 */ 112, 87, 88, 36, 4, 91, 92, 93, 109, 167, + /* 1150 */ 122, 123, 80, 109, 47, 84, 4, 85, 87, 88, + /* 1160 */ 81, 31, 106, 109, 122, 156, 100, 87, 88, 10, + /* 1170 */ 63, 64, 87, 88, 167, 166, 80, 47, 71, 84, + /* 1180 */ 120, 85, 87, 88, 130, 203, 79, 133, 80, 135, + /* 1190 */ 136, 84, 4, 85, 87, 88, 111, 105, 91, 92, + /* 1200 */ 93, 84, 122, 4, 87, 88, 4, 87, 88, 48, + /* 1210 */ 203, 4, 48, 131, 143, 156, 109, 79, 20, 87, + /* 1220 */ 88, 156, 87, 88, 36, 166, 80, 87, 88, 4, + /* 1230 */ 100, 166, 72, 81, 72, 47, 106, 130, 33, 87, + /* 1240 */ 133, 146, 135, 136, 109, 138, 187, 33, 4, 109, + /* 1250 */ 4, 63, 64, 170, 171, 172, 156, 4, 158, 71, + /* 1260 */ 85, 144, 130, 141, 4, 145, 166, 79, 156, 204, + /* 1270 */ 205, 119, 84, 212, 79, 87, 88, 4, 166, 91, + /* 1280 */ 92, 93, 36, 222, 4, 100, 87, 88, 229, 87, + /* 1290 */ 88, 4, 109, 47, 87, 88, 120, 109, 121, 17, + /* 1300 */ 4, 144, 4, 87, 80, 117, 80, 165, 109, 63, + /* 1310 */ 64, 109, 87, 88, 231, 222, 36, 71, 130, 81, + /* 1320 */ 206, 133, 210, 135, 136, 79, 80, 47, 63, 64, + /* 1330 */ 84, 87, 88, 87, 88, 156, 156, 91, 92, 93, + /* 1340 */ 87, 88, 179, 63, 64, 166, 166, 87, 88, 142, + /* 1350 */ 179, 71, 156, 109, 158, 109, 91, 92, 154, 79, + /* 1360 */ 87, 88, 166, 155, 84, 112, 187, 87, 88, 100, + /* 1370 */ 73, 91, 92, 93, 87, 88, 130, 156, 156, 133, + /* 1380 */ 158, 135, 136, 87, 88, 87, 88, 166, 166, 109, + /* 1390 */ 170, 171, 172, 170, 171, 172, 154, 156, 170, 171, + /* 1400 */ 172, 156, 154, 143, 170, 171, 172, 166, 229, 154, + /* 1410 */ 130, 166, 160, 133, 156, 135, 136, 130, 177, 146, + /* 1420 */ 85, 155, 177, 156, 166, 158, 130, 156, 130, 155, + /* 1430 */ 183, 28, 156, 166, 156, 177, 155, 166, 117, 156, + /* 1440 */ 155, 192, 166, 129, 166, 212, 156, 156, 177, 166, + /* 1450 */ 199, 231, 198, 177, 231, 177, 166, 166, 197, 231, + /* 1460 */ 177, 127, 221, 200, 126, 231, 221, 177, 177, 124, + /* 1470 */ 128, 201, 99, 156, 156, 156, 68, 47, 222, 221, + /* 1480 */ 156, 156, 225, 166, 166, 166, 138, 82, 156, 166, + /* 1490 */ 166, 166, 221, 156, 177, 177, 177, 221, 166, 221, + /* 1500 */ 156, 177, 177, 166, 221, 156, 156, 4, 156, 177, + /* 1510 */ 166, 221, 221, 156, 156, 166, 166, 209, 166, 209, + /* 1520 */ 82, 177, 156, 166, 166, 155, 177, 177, 166, 177, + /* 1530 */ 79, 156, 166, 178, 177, 177, 183, 155, 221, 221, + /* 1540 */ 221, 166, 155, 177, 155, 221, 221, 156, 156, 156, + /* 1550 */ 156, 156, 177, 221, 156, 178, 156, 166, 166, 166, + /* 1560 */ 166, 166, 155, 209, 166, 221, 166, 82, 177, 177, + /* 1570 */ 221, 221, 177, 221, 156, 177, 156, 177, 221, 221, + /* 1580 */ 156, 156, 158, 156, 166, 209, 166, 221, 82, 24, + /* 1590 */ 166, 166, 82, 166, 234, 177, 221, 177, 235, 156, + /* 1600 */ 138, 158, 177, 197, 177, 156, 190, 45, 156, 166, + /* 1610 */ 156, 82, 221, 221, 156, 166, 221, 156, 166, 221, + /* 1620 */ 166, 221, 156, 156, 166, 156, 82, 166, 219, 79, + /* 1630 */ 156, 190, 166, 166, 82, 166, 82, 156, 209, 221, + /* 1640 */ 166, 221, 156, 155, 209, 156, 221, 166, 221, 156, + /* 1650 */ 156, 156, 166, 82, 82, 166, 15, 238, 190, 166, + /* 1660 */ 166, 166, 239, 121, 238, 194, 176, 231, 196, 185, + /* 1670 */ 231, 226, 176, 208, 176, 197, 167, 233, 181, 209, + /* 1680 */ 167, 167, 182, 182, 182, 167, 223, 232, 240, 207, + /* 1690 */ 240, 227, 193, 189, 186, 240, 189, +}; +#define YY_SHIFT_USE_DFLT (-101) +#define YY_SHIFT_COUNT (343) +#define YY_SHIFT_MIN (-100) +#define YY_SHIFT_MAX (1641) +static const short yy_shift_ofst[] = { + /* 0 */ 273, -7, 499, -4, 141, 892, 1246, 1188, 533, 533, + /* 10 */ 13, 577, 567, 1107, 1280, 725, 81, 366, 1054, 973, + /* 20 */ 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + /* 30 */ 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + /* 40 */ 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1028, 43, + /* 50 */ 1296, 1296, 1296, 1296, 1265, 1265, 1296, 1265, 1265, 1265, + /* 60 */ 524, 173, 929, 1117, 1095, 1071, 1044, 1039, 1010, 986, + /* 70 */ 251, 1287, 1287, 1298, 1130, 1296, 1287, 255, 301, 153, + /* 80 */ 917, 853, 843, 540, 202, 531, 1273, 1260, 1253, 1244, + /* 90 */ 1207, 1202, 1132, 1199, 1140, 1135, 1120, 1085, 731, 816, + /* 100 */ 915, 728, 1080, 1042, 1152, 1152, 611, 1152, 990, 625, + /* 110 */ 942, 195, 605, 1225, 1225, 1225, 1225, 1225, 1225, 1225, + /* 120 */ 1225, -30, 1033, 167, 1321, 1321, -101, 502, 502, 502, + /* 130 */ 502, 502, 502, 502, 527, 327, 659, 778, 742, 716, + /* 140 */ 692, 359, 804, 804, 827, 850, 850, 405, 405, 405, + /* 150 */ 405, 204, 211, 1, 233, 1, 181, 522, 35, 375, + /* 160 */ -20, 121, -76, -100, -100, 858, 426, -100, -100, 166, + /* 170 */ 690, 690, 690, 200, 200, 200, 566, 162, 1108, 164, + /* 180 */ 93, 918, 164, 550, 678, 678, 565, 41, 164, 1641, + /* 190 */ 1542, 1641, 1552, 1562, 1572, 1571, 1554, 1269, 1552, 1562, + /* 200 */ 1550, 1544, 1529, 1562, 1314, 1510, 1462, 1565, 1506, 1485, + /* 210 */ 1451, 1269, 1269, 1269, 1269, 1403, 1503, 1451, 1269, 1438, + /* 220 */ 1503, 1405, 1348, 1430, 1408, 1373, 1342, 1345, 1334, 1338, + /* 230 */ 1314, 1321, 1269, 1269, 1403, 1269, 1269, 1335, 1297, 1297, + /* 240 */ 1297, 1269, 1297, -101, -101, -101, -101, -101, -101, -101, + /* 250 */ -101, 458, 15, 880, 349, 848, 564, 1096, 706, 1072, + /* 260 */ 983, 901, 590, 582, 451, 241, 449, 86, 38, -11, + /* 270 */ 55, 28, -59, 1238, 1157, 1226, 1224, 1216, 1282, 1177, + /* 280 */ 1176, 1183, 1185, 1195, 1122, 1214, 1175, 1205, 1198, 1162, + /* 290 */ 1160, 1146, 1138, 1082, 1164, 1161, 1092, 1060, 1159, 1066, + /* 300 */ 1079, 1056, 1012, 1017, 1037, 1006, 975, 972, 1018, 954, + /* 310 */ 935, 932, 928, 905, 896, 845, 884, 883, 860, 813, + /* 320 */ 860, 854, 815, 785, 840, 801, 800, 736, 617, 636, + /* 330 */ 588, 559, 555, 532, 316, 498, 418, 407, 317, 178, + /* 340 */ 143, 87, 130, -41, +}; +#define YY_REDUCE_USE_DFLT (-194) +#define YY_REDUCE_COUNT (250) +#define YY_REDUCE_MIN (-193) +#define YY_REDUCE_MAX (1518) +static const short yy_reduce_ofst[] = { + /* 0 */ 192, -34, 774, -57, 782, 318, -148, 879, 776, 374, + /* 10 */ 132, 29, 320, 512, 466, 277, 1427, 1425, 1420, 1418, + /* 20 */ 1400, 1398, 1395, 1392, 1391, 1375, 1366, 1358, 1357, 1352, + /* 30 */ 1350, 1349, 1344, 1332, 1325, 1324, 1319, 1318, 1317, 1291, + /* 40 */ 1290, 1283, 1278, 1276, 1271, 1258, 1245, 1241, 1065, 795, + /* 50 */ 1179, 1059, 906, 893, 1234, 1228, -75, 1223, 1220, 1083, + /* 60 */ 280, 431, 286, 920, 1443, 1424, 1267, 1222, 1196, 1100, + /* 70 */ 925, 1112, 788, 470, 297, -131, 46, 866, 1061, 307, + /* 80 */ 1489, 1495, 1494, 936, 1493, 1489, 936, 936, 1486, 936, + /* 90 */ 936, 936, 1481, 936, 936, 936, 1474, 1469, 936, 1467, + /* 100 */ 1466, 1461, 1458, 1454, 1007, 982, 1452, 864, 1449, 936, + /* 110 */ 1394, 1393, 1337, 1221, 1180, 1009, 948, 936, 770, 215, + /* 120 */ -80, 796, 634, 656, 242, -45, -193, 1093, 1093, 1093, + /* 130 */ 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, + /* 140 */ 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, + /* 150 */ 1093, 1093, 1093, 1507, 1508, 1504, 1499, 1482, 1499, 1464, + /* 160 */ 1463, 1093, 1499, 1499, 1499, 1444, 1455, 1499, 1499, 1518, + /* 170 */ 1502, 1501, 1500, 1514, 1513, 1509, 1470, 1478, 1484, 1498, + /* 180 */ 1497, 1465, 1496, 1445, 1439, 1436, 1472, 1471, 1490, 1426, + /* 190 */ 1423, 1419, 1435, 1468, 1354, 1354, 1354, 1488, 1429, 1441, + /* 200 */ 1409, 1354, 1354, 1416, 1406, 1354, 1363, 1360, 1376, 1354, + /* 210 */ 1377, 1407, 1389, 1387, 1382, 1353, 1362, 1355, 1370, 1310, + /* 220 */ 1323, 1308, 1257, 1256, 1093, 1233, 1270, 1263, 1251, 1254, + /* 230 */ 1261, 1249, 1285, 1281, 1247, 1274, 1266, 1252, 1255, 1248, + /* 240 */ 1242, 1208, 1204, 1171, 1163, 1114, 1093, 1093, 1093, 1093, + /* 250 */ 1142, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 589, 936, 936, 862, 903, 851, 851, 851, 936, 936, + /* 10 */ 730, 936, 851, 851, 851, 936, 851, 851, 851, 851, + /* 20 */ 851, 851, 851, 851, 851, 851, 851, 851, 851, 851, + /* 30 */ 851, 851, 851, 851, 851, 851, 851, 851, 851, 851, + /* 40 */ 851, 851, 851, 851, 851, 851, 851, 851, 724, 608, + /* 50 */ 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, + /* 60 */ 616, 720, 730, 936, 936, 936, 936, 790, 777, 768, + /* 70 */ 936, 800, 800, 783, 679, 936, 800, 756, 752, 936, + /* 80 */ 839, 936, 936, 731, 936, 839, 936, 936, 936, 936, + /* 90 */ 936, 791, 784, 778, 769, 936, 936, 936, 936, 936, + /* 100 */ 936, 936, 936, 936, 720, 720, 936, 720, 936, 936, + /* 110 */ 936, 840, 594, 936, 882, 936, 936, 936, 936, 936, + /* 120 */ 936, 605, 589, 936, 936, 936, 710, 736, 773, 761, + /* 130 */ 863, 856, 857, 855, 852, 852, 852, 852, 852, 852, + /* 140 */ 852, 852, 852, 823, 816, 827, 815, 831, 841, 826, + /* 150 */ 818, 817, 819, 936, 936, 936, 936, 723, 936, 936, + /* 160 */ 936, 820, 936, 789, 698, 936, 910, 693, 601, 618, + /* 170 */ 936, 936, 936, 936, 936, 936, 936, 772, 670, 756, + /* 180 */ 645, 738, 756, 858, 936, 936, 721, 708, 756, 934, + /* 190 */ 931, 934, 739, 683, 739, 739, 739, 681, 739, 683, + /* 200 */ 796, 739, 739, 683, 772, 739, 918, 915, 739, 739, + /* 210 */ 871, 681, 681, 681, 681, 662, 936, 871, 681, 739, + /* 220 */ 936, 739, 936, 852, 821, 752, 762, 748, 760, 757, + /* 230 */ 772, 936, 681, 681, 662, 681, 681, 665, 593, 593, + /* 240 */ 593, 681, 593, 649, 649, 726, 830, 829, 828, 822, + /* 250 */ 629, 864, 936, 936, 936, 936, 936, 936, 936, 936, + /* 260 */ 936, 936, 936, 936, 936, 936, 936, 936, 936, 763, + /* 270 */ 936, 936, 936, 936, 936, 936, 936, 936, 936, 881, + /* 280 */ 936, 936, 936, 936, 936, 936, 914, 913, 936, 936, + /* 290 */ 936, 936, 936, 936, 936, 936, 936, 936, 902, 936, + /* 300 */ 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, + /* 310 */ 936, 936, 936, 936, 936, 936, 936, 936, 758, 936, + /* 320 */ 861, 843, 701, 850, 936, 936, 936, 936, 936, 842, + /* 330 */ 853, 810, 936, 749, 936, 809, 806, 808, 611, 936, + /* 340 */ 936, 936, 936, 936, 586, 590, 935, 933, 932, 930, + /* 350 */ 890, 889, 888, 886, 895, 894, 893, 892, 891, 887, + /* 360 */ 885, 884, 883, 880, 787, 775, 766, 697, 929, 927, + /* 370 */ 928, 879, 877, 878, 696, 695, 692, 691, 690, 869, + /* 380 */ 868, 866, 865, 867, 604, 906, 909, 908, 907, 912, + /* 390 */ 911, 904, 917, 916, 921, 925, 924, 923, 922, 920, + /* 400 */ 901, 795, 794, 792, 797, 788, 793, 780, 786, 785, + /* 410 */ 776, 779, 684, 771, 767, 770, 905, 694, 603, 741, + /* 420 */ 602, 607, 668, 669, 677, 680, 675, 678, 674, 673, + /* 430 */ 672, 676, 671, 667, 610, 609, 623, 621, 622, 620, + /* 440 */ 619, 617, 639, 638, 635, 637, 634, 636, 633, 632, + /* 450 */ 631, 630, 628, 661, 647, 646, 874, 876, 875, 873, + /* 460 */ 872, 654, 653, 659, 658, 657, 656, 652, 655, 651, + /* 470 */ 650, 648, 644, 814, 813, 807, 835, 707, 706, 715, + /* 480 */ 713, 712, 711, 747, 746, 745, 744, 743, 742, 735, + /* 490 */ 733, 729, 728, 734, 732, 727, 719, 717, 718, 716, + /* 500 */ 612, 802, 799, 801, 798, 737, 725, 722, 709, 751, + /* 510 */ 753, 854, 844, 834, 845, 740, 832, 833, 704, 703, + /* 520 */ 702, 853, 850, 846, 926, 838, 849, 837, 836, 825, + /* 530 */ 824, 812, 847, 848, 811, 750, 765, 898, 897, 900, + /* 540 */ 899, 896, 764, 625, 624, 705, 700, 699, 805, 804, + /* 550 */ 803, 643, 755, 754, 642, 664, 663, 660, 641, 640, + /* 560 */ 627, 626, 606, 600, 599, 598, 597, 615, 614, 613, + /* 570 */ 611, 596, 595, 689, 688, 687, 686, 685, 682, 592, + /* 580 */ 591, 588, 587, 585, +}; + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* ILLEGAL => nothing */ + 0, /* COMMENT => nothing */ + 0, /* SPACE => nothing */ + 0, /* ID => nothing */ + 4, /* ABORT => ID */ + 4, /* AFTER => ID */ + 4, /* ASC => ID */ + 4, /* ATTACH => ID */ + 4, /* BEFORE => ID */ + 4, /* BEGIN => ID */ + 4, /* CASCADE => ID */ + 4, /* CLUSTER => ID */ + 4, /* CONFLICT => ID */ + 4, /* COPY => ID */ + 4, /* DATABASE => ID */ + 4, /* DEFERRED => ID */ + 4, /* DELIMITERS => ID */ + 4, /* DESC => ID */ + 4, /* DETACH => ID */ + 4, /* EACH => ID */ + 4, /* END => ID */ + 4, /* EXPLAIN => ID */ + 4, /* FAIL => ID */ + 4, /* FOR => ID */ + 4, /* GLOB => ID */ + 4, /* IGNORE => ID */ + 4, /* IMMEDIATE => ID */ + 4, /* INITIALLY => ID */ + 4, /* INSTEAD => ID */ + 4, /* LIKE => ID */ + 4, /* MATCH => ID */ + 4, /* KEY => ID */ + 4, /* OF => ID */ + 4, /* OFFSET => ID */ + 4, /* PRAGMA => ID */ + 4, /* RAISE => ID */ + 4, /* REPLACE => ID */ + 4, /* RESTRICT => ID */ + 4, /* ROW => ID */ + 4, /* STATEMENT => ID */ + 4, /* TEMP => ID */ + 4, /* TRIGGER => ID */ + 4, /* VACUUM => ID */ + 4, /* VIEW => ID */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ + QList* tokens = nullptr; +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyidxMax; /* Maximum value of yyidx */ +#endif + int yyerrcnt; /* Shifts left before out of the error */ + sqlite2_parseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +void *sqlite2_parseCopyParserState(void* other) +{ + yyParser *pParser; + yyParser *otherParser = (yyParser*)other; + + // Copy parser + pParser = (yyParser*)malloc((size_t)sizeof(yyParser)); + memcpy(pParser, other, (size_t)sizeof(yyParser)); + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = malloc((size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)other)->yystack, (size_t)stackSize); +#endif + + for (int i = 0; i <= pParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList(); + *(pParser->yystack[i].tokens) = *(otherParser->yystack[i].tokens); + } + + return pParser; +} + +void sqlite2_parseAddToken(void* other, Token* token) +{ + yyParser *otherParser = (yyParser*)other; + if (otherParser->yyidx < 0) + return; // Nothing on stack yet. Might happen when parsing just whitespaces, nothing else. + + otherParser->yystack[otherParser->yyidx].tokens->append(token); +} + +void sqlite2_parseRestoreParserState(void* saved, void* target) +{ + yyParser *pParser = (yyParser*)target; + yyParser *savedParser = (yyParser*)saved; + + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + + memcpy(pParser, saved, (size_t)sizeof(yyParser)); + + for (int i = 0; i <= savedParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList(); + *(pParser->yystack[i].tokens) = *(savedParser->yystack[i].tokens); + } + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = relloc(pParser->yystack, (size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)saved)->yystack, (size_t)stackSize); +#endif +} + +void sqlite2_parseFreeSavedState(void* other) +{ + yyParser *pParser = (yyParser*)other; + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + free(other); +} + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
    +**
  • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
  • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
+** +** Outputs: +** None. +*/ +void sqlite2_parseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + "$", "ILLEGAL", "COMMENT", "SPACE", + "ID", "ABORT", "AFTER", "ASC", + "ATTACH", "BEFORE", "BEGIN", "CASCADE", + "CLUSTER", "CONFLICT", "COPY", "DATABASE", + "DEFERRED", "DELIMITERS", "DESC", "DETACH", + "EACH", "END", "EXPLAIN", "FAIL", + "FOR", "GLOB", "IGNORE", "IMMEDIATE", + "INITIALLY", "INSTEAD", "LIKE", "MATCH", + "KEY", "OF", "OFFSET", "PRAGMA", + "RAISE", "REPLACE", "RESTRICT", "ROW", + "STATEMENT", "TEMP", "TRIGGER", "VACUUM", + "VIEW", "OR", "AND", "NOT", + "EQ", "NE", "ISNULL", "NOTNULL", + "IS", "BETWEEN", "IN", "GT", + "GE", "LT", "LE", "BITAND", + "BITOR", "LSHIFT", "RSHIFT", "PLUS", + "MINUS", "STAR", "SLASH", "REM", + "CONCAT", "UMINUS", "UPLUS", "BITNOT", + "SEMI", "TRANSACTION", "ID_TRANS", "COMMIT", + "ROLLBACK", "CREATE", "TABLE", "LP", + "RP", "AS", "DOT", "ID_TAB_NEW", + "ID_DB", "COMMA", "ID_COL_NEW", "STRING", + "JOIN_KW", "ID_COL_TYPE", "DEFAULT", "INTEGER", + "FLOAT", "NULL", "CONSTRAINT", "PRIMARY", + "UNIQUE", "CHECK", "REFERENCES", "COLLATE", + "ON", "INSERT", "DELETE", "UPDATE", + "ID_FK_MATCH", "SET", "DEFERRABLE", "FOREIGN", + "ID_CONSTR", "ID_TAB", "DROP", "ID_VIEW_NEW", + "ID_VIEW", "UNION", "ALL", "EXCEPT", + "INTERSECT", "SELECT", "DISTINCT", "ID_ALIAS", + "FROM", "USING", "JOIN", "ID_JOIN_OPTS", + "ORDER", "BY", "GROUP", "HAVING", + "LIMIT", "WHERE", "ID_COL", "INTO", + "VALUES", "VARIABLE", "LIKE_KW", "CASE", + "ID_FN", "ID_ERR_MSG", "WHEN", "THEN", + "ELSE", "INDEX", "ID_IDX_NEW", "ID_IDX", + "ID_PRAGMA", "ID_TRIG_NEW", "ID_TRIG", "error", + "cmd", "input", "cmdlist", "ecmd", + "explain", "cmdx", "trans_opt", "onconf", + "nm", "temp", "fullname", "columnlist", + "conslist_opt", "select", "column", "columnid", + "type", "carglist", "id", "ids", + "typetoken", "typename", "signed", "plus_num", + "minus_num", "ccons", "ccons_nm", "carg", + "sortorder", "expr", "idxlist_opt", "refargs", + "defer_subclause", "refarg", "refact", "init_deferred_pred_opt", + "conslist", "tconscomma", "tcons", "idxlist", + "defer_subclause_opt", "resolvetype", "orconf", "select_stmt", + "oneselect", "multiselect_op", "distinct", "selcollist", + "from", "where_opt", "groupby_opt", "having_opt", + "orderby_opt", "limit_opt", "sclp", "as", + "joinsrc", "singlesrc", "seltablist", "joinop", + "joinconstr_opt", "dbnm", "inscollist", "sortlist", + "collate", "nexprlist", "delete_stmt", "update_stmt", + "setlist", "insert_stmt", "insert_cmd", "inscollist_opt", + "exprlist", "exprx", "not_opt", "likeop", + "case_operand", "case_exprlist", "case_else", "raisetype", + "uniqueflag", "idxlist_single", "nmnum", "number", + "trigger_time", "trigger_event", "foreach_clause", "when_clause", + "trigger_cmd_list", "trigger_cmd", "database_kw_opt", "key_opt", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "input ::= cmdlist", + /* 1 */ "cmdlist ::= cmdlist ecmd", + /* 2 */ "cmdlist ::= ecmd", + /* 3 */ "ecmd ::= SEMI", + /* 4 */ "ecmd ::= explain cmdx SEMI", + /* 5 */ "explain ::=", + /* 6 */ "explain ::= EXPLAIN", + /* 7 */ "cmdx ::= cmd", + /* 8 */ "cmd ::= BEGIN trans_opt onconf", + /* 9 */ "trans_opt ::=", + /* 10 */ "trans_opt ::= TRANSACTION", + /* 11 */ "trans_opt ::= TRANSACTION nm", + /* 12 */ "trans_opt ::= TRANSACTION ID_TRANS", + /* 13 */ "cmd ::= COMMIT trans_opt", + /* 14 */ "cmd ::= END trans_opt", + /* 15 */ "cmd ::= ROLLBACK trans_opt", + /* 16 */ "cmd ::= CREATE temp TABLE fullname LP columnlist conslist_opt RP", + /* 17 */ "cmd ::= CREATE temp TABLE fullname AS select", + /* 18 */ "cmd ::= CREATE temp TABLE nm DOT ID_TAB_NEW", + /* 19 */ "cmd ::= CREATE temp TABLE ID_DB|ID_TAB_NEW", + /* 20 */ "temp ::= TEMP", + /* 21 */ "temp ::=", + /* 22 */ "columnlist ::= columnlist COMMA column", + /* 23 */ "columnlist ::= column", + /* 24 */ "column ::= columnid type carglist", + /* 25 */ "columnid ::= nm", + /* 26 */ "columnid ::= ID_COL_NEW", + /* 27 */ "id ::= ID", + /* 28 */ "ids ::= ID|STRING", + /* 29 */ "nm ::= id", + /* 30 */ "nm ::= STRING", + /* 31 */ "nm ::= JOIN_KW", + /* 32 */ "type ::=", + /* 33 */ "type ::= typetoken", + /* 34 */ "typetoken ::= typename", + /* 35 */ "typetoken ::= typename LP signed RP", + /* 36 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 37 */ "typename ::= ids", + /* 38 */ "typename ::= typename ids", + /* 39 */ "typename ::= ID_COL_TYPE", + /* 40 */ "signed ::= plus_num", + /* 41 */ "signed ::= minus_num", + /* 42 */ "carglist ::= carglist ccons", + /* 43 */ "carglist ::= carglist ccons_nm ccons", + /* 44 */ "carglist ::= carglist carg", + /* 45 */ "carglist ::=", + /* 46 */ "carg ::= DEFAULT STRING", + /* 47 */ "carg ::= DEFAULT ID", + /* 48 */ "carg ::= DEFAULT INTEGER", + /* 49 */ "carg ::= DEFAULT PLUS INTEGER", + /* 50 */ "carg ::= DEFAULT MINUS INTEGER", + /* 51 */ "carg ::= DEFAULT FLOAT", + /* 52 */ "carg ::= DEFAULT PLUS FLOAT", + /* 53 */ "carg ::= DEFAULT MINUS FLOAT", + /* 54 */ "carg ::= DEFAULT NULL", + /* 55 */ "ccons_nm ::= CONSTRAINT nm", + /* 56 */ "ccons ::= NULL onconf", + /* 57 */ "ccons ::= NOT NULL onconf", + /* 58 */ "ccons ::= PRIMARY KEY sortorder onconf", + /* 59 */ "ccons ::= UNIQUE onconf", + /* 60 */ "ccons ::= CHECK LP expr RP onconf", + /* 61 */ "ccons ::= REFERENCES nm idxlist_opt refargs", + /* 62 */ "ccons ::= defer_subclause", + /* 63 */ "ccons ::= COLLATE id", + /* 64 */ "ccons ::= CHECK LP RP", + /* 65 */ "refargs ::=", + /* 66 */ "refargs ::= refargs refarg", + /* 67 */ "refarg ::= MATCH nm", + /* 68 */ "refarg ::= ON INSERT refact", + /* 69 */ "refarg ::= ON DELETE refact", + /* 70 */ "refarg ::= ON UPDATE refact", + /* 71 */ "refarg ::= MATCH ID_FK_MATCH", + /* 72 */ "refact ::= SET NULL", + /* 73 */ "refact ::= SET DEFAULT", + /* 74 */ "refact ::= CASCADE", + /* 75 */ "refact ::= RESTRICT", + /* 76 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 77 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 78 */ "init_deferred_pred_opt ::=", + /* 79 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 80 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 81 */ "conslist_opt ::=", + /* 82 */ "conslist_opt ::= COMMA conslist", + /* 83 */ "conslist ::= conslist tconscomma tcons", + /* 84 */ "conslist ::= tcons", + /* 85 */ "tconscomma ::= COMMA", + /* 86 */ "tconscomma ::=", + /* 87 */ "tcons ::= CONSTRAINT nm", + /* 88 */ "tcons ::= PRIMARY KEY LP idxlist RP onconf", + /* 89 */ "tcons ::= UNIQUE LP idxlist RP onconf", + /* 90 */ "tcons ::= CHECK LP expr RP onconf", + /* 91 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt", + /* 92 */ "tcons ::= CONSTRAINT ID_CONSTR", + /* 93 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES ID_TAB", + /* 94 */ "tcons ::= CHECK LP RP onconf", + /* 95 */ "defer_subclause_opt ::=", + /* 96 */ "defer_subclause_opt ::= defer_subclause", + /* 97 */ "onconf ::=", + /* 98 */ "onconf ::= ON CONFLICT resolvetype", + /* 99 */ "orconf ::=", + /* 100 */ "orconf ::= OR resolvetype", + /* 101 */ "resolvetype ::= ROLLBACK", + /* 102 */ "resolvetype ::= ABORT", + /* 103 */ "resolvetype ::= FAIL", + /* 104 */ "resolvetype ::= IGNORE", + /* 105 */ "resolvetype ::= REPLACE", + /* 106 */ "cmd ::= DROP TABLE fullname", + /* 107 */ "cmd ::= DROP TABLE nm DOT ID_TAB", + /* 108 */ "cmd ::= DROP TABLE ID_DB|ID_TAB", + /* 109 */ "cmd ::= CREATE temp VIEW nm AS select", + /* 110 */ "cmd ::= CREATE temp VIEW ID_VIEW_NEW", + /* 111 */ "cmd ::= DROP VIEW nm", + /* 112 */ "cmd ::= DROP VIEW ID_VIEW", + /* 113 */ "cmd ::= select_stmt", + /* 114 */ "select_stmt ::= select", + /* 115 */ "select ::= oneselect", + /* 116 */ "select ::= select multiselect_op oneselect", + /* 117 */ "multiselect_op ::= UNION", + /* 118 */ "multiselect_op ::= UNION ALL", + /* 119 */ "multiselect_op ::= EXCEPT", + /* 120 */ "multiselect_op ::= INTERSECT", + /* 121 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 122 */ "distinct ::= DISTINCT", + /* 123 */ "distinct ::= ALL", + /* 124 */ "distinct ::=", + /* 125 */ "sclp ::= selcollist COMMA", + /* 126 */ "sclp ::=", + /* 127 */ "selcollist ::= sclp expr as", + /* 128 */ "selcollist ::= sclp STAR", + /* 129 */ "selcollist ::= sclp nm DOT STAR", + /* 130 */ "selcollist ::= sclp", + /* 131 */ "selcollist ::= sclp ID_TAB DOT STAR", + /* 132 */ "as ::= AS nm", + /* 133 */ "as ::= ids", + /* 134 */ "as ::= AS ID_ALIAS", + /* 135 */ "as ::= ID_ALIAS", + /* 136 */ "as ::=", + /* 137 */ "from ::=", + /* 138 */ "from ::= FROM joinsrc", + /* 139 */ "joinsrc ::= singlesrc seltablist", + /* 140 */ "joinsrc ::=", + /* 141 */ "seltablist ::= seltablist joinop singlesrc joinconstr_opt", + /* 142 */ "seltablist ::=", + /* 143 */ "singlesrc ::= nm dbnm as", + /* 144 */ "singlesrc ::= LP select RP as", + /* 145 */ "singlesrc ::= LP joinsrc RP as", + /* 146 */ "singlesrc ::=", + /* 147 */ "singlesrc ::= nm DOT", + /* 148 */ "singlesrc ::= nm DOT ID_TAB", + /* 149 */ "singlesrc ::= ID_DB|ID_TAB", + /* 150 */ "singlesrc ::= nm DOT ID_VIEW", + /* 151 */ "singlesrc ::= ID_DB|ID_VIEW", + /* 152 */ "joinconstr_opt ::= ON expr", + /* 153 */ "joinconstr_opt ::= USING LP inscollist RP", + /* 154 */ "joinconstr_opt ::=", + /* 155 */ "dbnm ::=", + /* 156 */ "dbnm ::= DOT nm", + /* 157 */ "fullname ::= nm dbnm", + /* 158 */ "joinop ::= COMMA", + /* 159 */ "joinop ::= JOIN", + /* 160 */ "joinop ::= JOIN_KW JOIN", + /* 161 */ "joinop ::= JOIN_KW nm JOIN", + /* 162 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 163 */ "joinop ::= ID_JOIN_OPTS", + /* 164 */ "orderby_opt ::=", + /* 165 */ "orderby_opt ::= ORDER BY sortlist", + /* 166 */ "sortlist ::= sortlist COMMA collate expr sortorder", + /* 167 */ "sortlist ::= expr collate sortorder", + /* 168 */ "collate ::=", + /* 169 */ "collate ::= COLLATE id", + /* 170 */ "sortorder ::= ASC", + /* 171 */ "sortorder ::= DESC", + /* 172 */ "sortorder ::=", + /* 173 */ "groupby_opt ::=", + /* 174 */ "groupby_opt ::= GROUP BY nexprlist", + /* 175 */ "groupby_opt ::= GROUP BY", + /* 176 */ "having_opt ::=", + /* 177 */ "having_opt ::= HAVING expr", + /* 178 */ "limit_opt ::=", + /* 179 */ "limit_opt ::= LIMIT signed", + /* 180 */ "limit_opt ::= LIMIT signed OFFSET signed", + /* 181 */ "limit_opt ::= LIMIT signed COMMA signed", + /* 182 */ "cmd ::= delete_stmt", + /* 183 */ "delete_stmt ::= DELETE FROM fullname where_opt", + /* 184 */ "delete_stmt ::= DELETE FROM", + /* 185 */ "delete_stmt ::= DELETE FROM nm DOT", + /* 186 */ "delete_stmt ::= DELETE FROM nm DOT ID_TAB", + /* 187 */ "delete_stmt ::= DELETE FROM ID_DB|ID_TAB", + /* 188 */ "where_opt ::=", + /* 189 */ "where_opt ::= WHERE expr", + /* 190 */ "where_opt ::= WHERE", + /* 191 */ "cmd ::= update_stmt", + /* 192 */ "update_stmt ::= UPDATE orconf fullname SET setlist where_opt", + /* 193 */ "update_stmt ::= UPDATE orconf", + /* 194 */ "update_stmt ::= UPDATE orconf nm DOT", + /* 195 */ "update_stmt ::= UPDATE orconf nm DOT ID_TAB", + /* 196 */ "update_stmt ::= UPDATE orconf ID_DB|ID_TAB", + /* 197 */ "setlist ::= setlist COMMA nm EQ expr", + /* 198 */ "setlist ::= nm EQ expr", + /* 199 */ "setlist ::=", + /* 200 */ "setlist ::= setlist COMMA", + /* 201 */ "setlist ::= setlist COMMA ID_COL", + /* 202 */ "setlist ::= ID_COL", + /* 203 */ "cmd ::= insert_stmt", + /* 204 */ "insert_stmt ::= insert_cmd INTO fullname inscollist_opt VALUES LP exprlist RP", + /* 205 */ "insert_stmt ::= insert_cmd INTO fullname inscollist_opt select", + /* 206 */ "insert_stmt ::= insert_cmd INTO", + /* 207 */ "insert_stmt ::= insert_cmd INTO nm DOT", + /* 208 */ "insert_stmt ::= insert_cmd INTO ID_DB|ID_TAB", + /* 209 */ "insert_stmt ::= insert_cmd INTO nm DOT ID_TAB", + /* 210 */ "insert_cmd ::= INSERT orconf", + /* 211 */ "insert_cmd ::= REPLACE", + /* 212 */ "inscollist_opt ::=", + /* 213 */ "inscollist_opt ::= LP inscollist RP", + /* 214 */ "inscollist ::= inscollist COMMA nm", + /* 215 */ "inscollist ::= nm", + /* 216 */ "inscollist ::=", + /* 217 */ "inscollist ::= inscollist COMMA ID_COL", + /* 218 */ "inscollist ::= ID_COL", + /* 219 */ "exprx ::= NULL", + /* 220 */ "exprx ::= INTEGER", + /* 221 */ "exprx ::= FLOAT", + /* 222 */ "exprx ::= STRING", + /* 223 */ "exprx ::= LP expr RP", + /* 224 */ "exprx ::= id", + /* 225 */ "exprx ::= JOIN_KW", + /* 226 */ "exprx ::= nm DOT nm", + /* 227 */ "exprx ::= nm DOT nm DOT nm", + /* 228 */ "exprx ::= VARIABLE", + /* 229 */ "exprx ::= ID LP exprlist RP", + /* 230 */ "exprx ::= ID LP STAR RP", + /* 231 */ "exprx ::= expr AND expr", + /* 232 */ "exprx ::= expr OR expr", + /* 233 */ "exprx ::= expr LT|GT|GE|LE expr", + /* 234 */ "exprx ::= expr EQ|NE expr", + /* 235 */ "exprx ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 236 */ "exprx ::= expr PLUS|MINUS expr", + /* 237 */ "exprx ::= expr STAR|SLASH|REM expr", + /* 238 */ "exprx ::= expr CONCAT expr", + /* 239 */ "exprx ::= expr not_opt likeop expr", + /* 240 */ "exprx ::= expr ISNULL|NOTNULL", + /* 241 */ "exprx ::= expr NOT NULL", + /* 242 */ "exprx ::= expr IS not_opt expr", + /* 243 */ "exprx ::= NOT expr", + /* 244 */ "exprx ::= BITNOT expr", + /* 245 */ "exprx ::= MINUS expr", + /* 246 */ "exprx ::= PLUS expr", + /* 247 */ "exprx ::= expr not_opt BETWEEN expr AND expr", + /* 248 */ "exprx ::= expr not_opt IN LP exprlist RP", + /* 249 */ "exprx ::= expr not_opt IN LP select RP", + /* 250 */ "exprx ::= expr not_opt IN nm dbnm", + /* 251 */ "exprx ::= LP select RP", + /* 252 */ "exprx ::= CASE case_operand case_exprlist case_else END", + /* 253 */ "exprx ::= RAISE LP raisetype COMMA nm RP", + /* 254 */ "exprx ::= RAISE LP IGNORE RP", + /* 255 */ "exprx ::= nm DOT", + /* 256 */ "exprx ::= nm DOT nm DOT", + /* 257 */ "exprx ::= expr not_opt BETWEEN expr", + /* 258 */ "exprx ::= CASE case_operand case_exprlist case_else", + /* 259 */ "exprx ::= expr not_opt IN LP exprlist", + /* 260 */ "exprx ::= expr not_opt IN ID_DB", + /* 261 */ "exprx ::= expr not_opt IN nm DOT ID_TAB", + /* 262 */ "exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN", + /* 263 */ "exprx ::= nm DOT ID_TAB|ID_COL", + /* 264 */ "exprx ::= nm DOT nm DOT ID_COL", + /* 265 */ "exprx ::= RAISE LP raisetype COMMA ID_ERR_MSG RP", + /* 266 */ "expr ::= exprx", + /* 267 */ "expr ::=", + /* 268 */ "not_opt ::=", + /* 269 */ "not_opt ::= NOT", + /* 270 */ "likeop ::= LIKE|GLOB", + /* 271 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 272 */ "case_exprlist ::= WHEN expr THEN expr", + /* 273 */ "case_else ::= ELSE expr", + /* 274 */ "case_else ::=", + /* 275 */ "case_operand ::= exprx", + /* 276 */ "case_operand ::=", + /* 277 */ "exprlist ::= nexprlist", + /* 278 */ "exprlist ::=", + /* 279 */ "nexprlist ::= nexprlist COMMA expr", + /* 280 */ "nexprlist ::= exprx", + /* 281 */ "cmd ::= CREATE uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf", + /* 282 */ "cmd ::= CREATE uniqueflag INDEX nm dbnm ON ID_TAB", + /* 283 */ "cmd ::= CREATE uniqueflag INDEX nm DOT ID_IDX_NEW", + /* 284 */ "cmd ::= CREATE uniqueflag INDEX ID_DB|ID_IDX_NEW", + /* 285 */ "uniqueflag ::= UNIQUE", + /* 286 */ "uniqueflag ::=", + /* 287 */ "idxlist_opt ::=", + /* 288 */ "idxlist_opt ::= LP idxlist RP", + /* 289 */ "idxlist ::= idxlist COMMA idxlist_single", + /* 290 */ "idxlist ::= idxlist_single", + /* 291 */ "idxlist_single ::= nm sortorder", + /* 292 */ "idxlist_single ::= ID_COL", + /* 293 */ "cmd ::= DROP INDEX fullname", + /* 294 */ "cmd ::= DROP INDEX nm DOT ID_IDX", + /* 295 */ "cmd ::= DROP INDEX ID_DB|ID_IDX", + /* 296 */ "cmd ::= COPY orconf nm dbnm FROM nm USING DELIMITERS STRING", + /* 297 */ "cmd ::= COPY orconf nm dbnm FROM nm", + /* 298 */ "cmd ::= VACUUM", + /* 299 */ "cmd ::= VACUUM nm", + /* 300 */ "cmd ::= PRAGMA ids", + /* 301 */ "cmd ::= PRAGMA nm EQ nmnum", + /* 302 */ "cmd ::= PRAGMA nm LP nmnum RP", + /* 303 */ "cmd ::= PRAGMA nm EQ minus_num", + /* 304 */ "cmd ::= PRAGMA nm LP minus_num RP", + /* 305 */ "cmd ::= PRAGMA nm DOT ID_PRAGMA", + /* 306 */ "cmd ::= PRAGMA ID_DB|ID_PRAGMA", + /* 307 */ "nmnum ::= plus_num", + /* 308 */ "nmnum ::= nm", + /* 309 */ "nmnum ::= ON", + /* 310 */ "nmnum ::= DELETE", + /* 311 */ "nmnum ::= DEFAULT", + /* 312 */ "plus_num ::= PLUS number", + /* 313 */ "plus_num ::= number", + /* 314 */ "minus_num ::= MINUS number", + /* 315 */ "number ::= INTEGER", + /* 316 */ "number ::= FLOAT", + /* 317 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause BEGIN trigger_cmd_list END", + /* 318 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause", + /* 319 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause BEGIN trigger_cmd_list", + /* 320 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON ID_TAB|ID_DB", + /* 321 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm DOT ID_TAB", + /* 322 */ "cmd ::= CREATE temp TRIGGER ID_TRIG_NEW", + /* 323 */ "trigger_time ::= BEFORE", + /* 324 */ "trigger_time ::= AFTER", + /* 325 */ "trigger_time ::= INSTEAD OF", + /* 326 */ "trigger_time ::=", + /* 327 */ "trigger_event ::= DELETE", + /* 328 */ "trigger_event ::= INSERT", + /* 329 */ "trigger_event ::= UPDATE", + /* 330 */ "trigger_event ::= UPDATE OF inscollist", + /* 331 */ "foreach_clause ::=", + /* 332 */ "foreach_clause ::= FOR EACH ROW", + /* 333 */ "foreach_clause ::= FOR EACH STATEMENT", + /* 334 */ "when_clause ::=", + /* 335 */ "when_clause ::= WHEN expr", + /* 336 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 337 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 338 */ "trigger_cmd ::= update_stmt", + /* 339 */ "trigger_cmd ::= insert_stmt", + /* 340 */ "trigger_cmd ::= delete_stmt", + /* 341 */ "trigger_cmd ::= select_stmt", + /* 342 */ "raisetype ::= ROLLBACK|ABORT|FAIL", + /* 343 */ "cmd ::= DROP TRIGGER fullname", + /* 344 */ "cmd ::= DROP TRIGGER nm DOT ID_TRIG", + /* 345 */ "cmd ::= DROP TRIGGER ID_DB|ID_TRIG", + /* 346 */ "cmd ::= ATTACH database_kw_opt ids AS ids key_opt", + /* 347 */ "key_opt ::=", + /* 348 */ "key_opt ::= USING ids", + /* 349 */ "database_kw_opt ::= DATABASE", + /* 350 */ "database_kw_opt ::=", + /* 351 */ "cmd ::= DETACH database_kw_opt nm", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. +*/ +static void yyGrowStack(yyParser *p){ + int newSize; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + if( pNew ){ + p->yystack = pNew; + p->yystksz = newSize; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", + yyTracePrompt, p->yystksz); + } +#endif + } +} +#endif + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to sqlite2_parse and sqlite2_parseFree. +*/ +void *sqlite2_parseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyidxMax = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yystack = NULL; + pParser->yystksz = 0; + yyGrowStack(pParser); +#endif + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + sqlite2_parseARG_FETCH; + if (parserContext->executeRules) + { + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + case 148: /* cmd */ + case 151: /* ecmd */ + case 153: /* cmdx */ + case 191: /* select_stmt */ + case 214: /* delete_stmt */ + case 215: /* update_stmt */ + case 217: /* insert_stmt */ + case 237: /* trigger_cmd */ +{ +delete (yypminor->yy203); +} + break; + case 152: /* explain */ +{ +delete (yypminor->yy91); +} + break; + case 154: /* trans_opt */ +{ +delete (yypminor->yy404); +} + break; + case 155: /* onconf */ + case 189: /* resolvetype */ + case 190: /* orconf */ +{ +delete (yypminor->yy418); +} + break; + case 156: /* nm */ + case 163: /* columnid */ + case 166: /* id */ + case 167: /* ids */ + case 169: /* typename */ + case 209: /* dbnm */ +{ +delete (yypminor->yy319); +} + break; + case 157: /* temp */ + case 194: /* distinct */ +{ +delete (yypminor->yy226); +} + break; + case 158: /* fullname */ +{ +delete (yypminor->yy120); +} + break; + case 159: /* columnlist */ +{ +delete (yypminor->yy42); +} + break; + case 160: /* conslist_opt */ + case 184: /* conslist */ +{ +delete (yypminor->yy13); +} + break; + case 161: /* select */ +{ +delete (yypminor->yy153); +} + break; + case 162: /* column */ +{ +delete (yypminor->yy147); +} + break; + case 164: /* type */ + case 168: /* typetoken */ +{ +delete (yypminor->yy57); +} + break; + case 165: /* carglist */ +{ +delete (yypminor->yy371); +} + break; + case 170: /* signed */ + case 171: /* plus_num */ + case 172: /* minus_num */ + case 230: /* nmnum */ + case 231: /* number */ +{ +delete (yypminor->yy69); +} + break; + case 173: /* ccons */ + case 174: /* ccons_nm */ + case 175: /* carg */ +{ +delete (yypminor->yy304); +} + break; + case 176: /* sortorder */ +{ +delete (yypminor->yy389); +} + break; + case 177: /* expr */ + case 197: /* where_opt */ + case 199: /* having_opt */ + case 221: /* exprx */ + case 224: /* case_operand */ + case 226: /* case_else */ +{ +delete (yypminor->yy192); +} + break; + case 178: /* idxlist_opt */ + case 187: /* idxlist */ +{ +delete (yypminor->yy63); +} + break; + case 179: /* refargs */ +{ +delete (yypminor->yy264); +} + break; + case 180: /* defer_subclause */ + case 188: /* defer_subclause_opt */ +{ +delete (yypminor->yy329); +} + break; + case 181: /* refarg */ +{ +delete (yypminor->yy187); +} + break; + case 182: /* refact */ +{ +delete (yypminor->yy424); +} + break; + case 183: /* init_deferred_pred_opt */ +{ +delete (yypminor->yy312); +} + break; + case 185: /* tconscomma */ + case 222: /* not_opt */ + case 228: /* uniqueflag */ + case 238: /* database_kw_opt */ +{ +delete (yypminor->yy291); +} + break; + case 186: /* tcons */ +{ +delete (yypminor->yy406); +} + break; + case 192: /* oneselect */ +{ +delete (yypminor->yy150); +} + break; + case 193: /* multiselect_op */ +{ +delete (yypminor->yy382); +} + break; + case 195: /* selcollist */ + case 202: /* sclp */ +{ +delete (yypminor->yy213); +} + break; + case 196: /* from */ + case 204: /* joinsrc */ +{ +delete (yypminor->yy31); +} + break; + case 198: /* groupby_opt */ + case 213: /* nexprlist */ + case 220: /* exprlist */ + case 225: /* case_exprlist */ +{ +delete (yypminor->yy231); +} + break; + case 200: /* orderby_opt */ + case 211: /* sortlist */ +{ +delete (yypminor->yy243); +} + break; + case 201: /* limit_opt */ +{ +delete (yypminor->yy324); +} + break; + case 203: /* as */ +{ +delete (yypminor->yy40); +} + break; + case 205: /* singlesrc */ +{ +delete (yypminor->yy121); +} + break; + case 206: /* seltablist */ +{ +delete (yypminor->yy131); +} + break; + case 207: /* joinop */ +{ +delete (yypminor->yy221); +} + break; + case 208: /* joinconstr_opt */ +{ +delete (yypminor->yy455); +} + break; + case 210: /* inscollist */ + case 219: /* inscollist_opt */ +{ +delete (yypminor->yy207); +} + break; + case 212: /* collate */ +{ +if ((yypminor->yy319)) delete (yypminor->yy319); +} + break; + case 216: /* setlist */ +{ +delete (yypminor->yy201); +} + break; + case 218: /* insert_cmd */ +{ +delete (yypminor->yy344); +} + break; + case 223: /* likeop */ +{ +delete (yypminor->yy41); +} + break; + case 229: /* idxlist_single */ +{ +delete (yypminor->yy428); +} + break; + case 232: /* trigger_time */ +{ +delete (yypminor->yy372); +} + break; + case 233: /* trigger_event */ +{ +delete (yypminor->yy151); +} + break; + case 234: /* foreach_clause */ +{ +delete (yypminor->yy83); +} + break; + case 235: /* when_clause */ + case 239: /* key_opt */ +{ +if ((yypminor->yy192)) delete (yypminor->yy192); +} + break; + case 236: /* trigger_cmd_list */ +{ +delete (yypminor->yy270); +} + break; + default: break; /* If no destructor action specified: do nothing */ + } + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + /* There is no mechanism by which the parser stack can be popped below + ** empty in SQLite. */ + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor(pParser, yymajor, &yytos->minor); + delete yytos->tokens; + yytos->tokens = nullptr; + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +**
    +**
  • A pointer to the parser. This should be a pointer +** obtained from sqlite2_parseAlloc. +**
  • A pointer to a function used to reclaim memory obtained +** from malloc. +**
+*/ +void sqlite2_parseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + /* In SQLite, we never try to destroy a parser that was not successfully + ** created in the first place. */ + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int sqlite2_parseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyidxMax; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + GET_CONTEXT; + + if( stateno>YY_SHIFT_COUNT + || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAheaddoFallbacks ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + int stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( i!=YY_REDUCE_USE_DFLT ); + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && iyyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ + + UNUSED_PARAMETER(yypMinor); + parserContext->error(QObject::tr("Parser stack overflow")); + sqlite2_parseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH ){ + yyStackOverflow(yypParser, yypMinor); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz ){ + yyStackOverflow(yypParser, yypMinor); + return; + } + } +#endif + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor = *yypMinor; + yytos->tokens = new QList(); +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { + { 149, 1 }, + { 150, 2 }, + { 150, 1 }, + { 151, 1 }, + { 151, 3 }, + { 152, 0 }, + { 152, 1 }, + { 153, 1 }, + { 148, 3 }, + { 154, 0 }, + { 154, 1 }, + { 154, 2 }, + { 154, 2 }, + { 148, 2 }, + { 148, 2 }, + { 148, 2 }, + { 148, 8 }, + { 148, 6 }, + { 148, 6 }, + { 148, 4 }, + { 157, 1 }, + { 157, 0 }, + { 159, 3 }, + { 159, 1 }, + { 162, 3 }, + { 163, 1 }, + { 163, 1 }, + { 166, 1 }, + { 167, 1 }, + { 156, 1 }, + { 156, 1 }, + { 156, 1 }, + { 164, 0 }, + { 164, 1 }, + { 168, 1 }, + { 168, 4 }, + { 168, 6 }, + { 169, 1 }, + { 169, 2 }, + { 169, 1 }, + { 170, 1 }, + { 170, 1 }, + { 165, 2 }, + { 165, 3 }, + { 165, 2 }, + { 165, 0 }, + { 175, 2 }, + { 175, 2 }, + { 175, 2 }, + { 175, 3 }, + { 175, 3 }, + { 175, 2 }, + { 175, 3 }, + { 175, 3 }, + { 175, 2 }, + { 174, 2 }, + { 173, 2 }, + { 173, 3 }, + { 173, 4 }, + { 173, 2 }, + { 173, 5 }, + { 173, 4 }, + { 173, 1 }, + { 173, 2 }, + { 173, 3 }, + { 179, 0 }, + { 179, 2 }, + { 181, 2 }, + { 181, 3 }, + { 181, 3 }, + { 181, 3 }, + { 181, 2 }, + { 182, 2 }, + { 182, 2 }, + { 182, 1 }, + { 182, 1 }, + { 180, 3 }, + { 180, 2 }, + { 183, 0 }, + { 183, 2 }, + { 183, 2 }, + { 160, 0 }, + { 160, 2 }, + { 184, 3 }, + { 184, 1 }, + { 185, 1 }, + { 185, 0 }, + { 186, 2 }, + { 186, 6 }, + { 186, 5 }, + { 186, 5 }, + { 186, 10 }, + { 186, 2 }, + { 186, 7 }, + { 186, 4 }, + { 188, 0 }, + { 188, 1 }, + { 155, 0 }, + { 155, 3 }, + { 190, 0 }, + { 190, 2 }, + { 189, 1 }, + { 189, 1 }, + { 189, 1 }, + { 189, 1 }, + { 189, 1 }, + { 148, 3 }, + { 148, 5 }, + { 148, 3 }, + { 148, 6 }, + { 148, 4 }, + { 148, 3 }, + { 148, 3 }, + { 148, 1 }, + { 191, 1 }, + { 161, 1 }, + { 161, 3 }, + { 193, 1 }, + { 193, 2 }, + { 193, 1 }, + { 193, 1 }, + { 192, 9 }, + { 194, 1 }, + { 194, 1 }, + { 194, 0 }, + { 202, 2 }, + { 202, 0 }, + { 195, 3 }, + { 195, 2 }, + { 195, 4 }, + { 195, 1 }, + { 195, 4 }, + { 203, 2 }, + { 203, 1 }, + { 203, 2 }, + { 203, 1 }, + { 203, 0 }, + { 196, 0 }, + { 196, 2 }, + { 204, 2 }, + { 204, 0 }, + { 206, 4 }, + { 206, 0 }, + { 205, 3 }, + { 205, 4 }, + { 205, 4 }, + { 205, 0 }, + { 205, 2 }, + { 205, 3 }, + { 205, 1 }, + { 205, 3 }, + { 205, 1 }, + { 208, 2 }, + { 208, 4 }, + { 208, 0 }, + { 209, 0 }, + { 209, 2 }, + { 158, 2 }, + { 207, 1 }, + { 207, 1 }, + { 207, 2 }, + { 207, 3 }, + { 207, 4 }, + { 207, 1 }, + { 200, 0 }, + { 200, 3 }, + { 211, 5 }, + { 211, 3 }, + { 212, 0 }, + { 212, 2 }, + { 176, 1 }, + { 176, 1 }, + { 176, 0 }, + { 198, 0 }, + { 198, 3 }, + { 198, 2 }, + { 199, 0 }, + { 199, 2 }, + { 201, 0 }, + { 201, 2 }, + { 201, 4 }, + { 201, 4 }, + { 148, 1 }, + { 214, 4 }, + { 214, 2 }, + { 214, 4 }, + { 214, 5 }, + { 214, 3 }, + { 197, 0 }, + { 197, 2 }, + { 197, 1 }, + { 148, 1 }, + { 215, 6 }, + { 215, 2 }, + { 215, 4 }, + { 215, 5 }, + { 215, 3 }, + { 216, 5 }, + { 216, 3 }, + { 216, 0 }, + { 216, 2 }, + { 216, 3 }, + { 216, 1 }, + { 148, 1 }, + { 217, 8 }, + { 217, 5 }, + { 217, 2 }, + { 217, 4 }, + { 217, 3 }, + { 217, 5 }, + { 218, 2 }, + { 218, 1 }, + { 219, 0 }, + { 219, 3 }, + { 210, 3 }, + { 210, 1 }, + { 210, 0 }, + { 210, 3 }, + { 210, 1 }, + { 221, 1 }, + { 221, 1 }, + { 221, 1 }, + { 221, 1 }, + { 221, 3 }, + { 221, 1 }, + { 221, 1 }, + { 221, 3 }, + { 221, 5 }, + { 221, 1 }, + { 221, 4 }, + { 221, 4 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 4 }, + { 221, 2 }, + { 221, 3 }, + { 221, 4 }, + { 221, 2 }, + { 221, 2 }, + { 221, 2 }, + { 221, 2 }, + { 221, 6 }, + { 221, 6 }, + { 221, 6 }, + { 221, 5 }, + { 221, 3 }, + { 221, 5 }, + { 221, 6 }, + { 221, 4 }, + { 221, 2 }, + { 221, 4 }, + { 221, 4 }, + { 221, 4 }, + { 221, 5 }, + { 221, 4 }, + { 221, 6 }, + { 221, 1 }, + { 221, 3 }, + { 221, 5 }, + { 221, 6 }, + { 177, 1 }, + { 177, 0 }, + { 222, 0 }, + { 222, 1 }, + { 223, 1 }, + { 225, 5 }, + { 225, 4 }, + { 226, 2 }, + { 226, 0 }, + { 224, 1 }, + { 224, 0 }, + { 220, 1 }, + { 220, 0 }, + { 213, 3 }, + { 213, 1 }, + { 148, 11 }, + { 148, 7 }, + { 148, 6 }, + { 148, 4 }, + { 228, 1 }, + { 228, 0 }, + { 178, 0 }, + { 178, 3 }, + { 187, 3 }, + { 187, 1 }, + { 229, 2 }, + { 229, 1 }, + { 148, 3 }, + { 148, 5 }, + { 148, 3 }, + { 148, 9 }, + { 148, 6 }, + { 148, 1 }, + { 148, 2 }, + { 148, 2 }, + { 148, 4 }, + { 148, 5 }, + { 148, 4 }, + { 148, 5 }, + { 148, 4 }, + { 148, 2 }, + { 230, 1 }, + { 230, 1 }, + { 230, 1 }, + { 230, 1 }, + { 230, 1 }, + { 171, 2 }, + { 171, 1 }, + { 172, 2 }, + { 231, 1 }, + { 231, 1 }, + { 148, 14 }, + { 148, 11 }, + { 148, 13 }, + { 148, 8 }, + { 148, 10 }, + { 148, 4 }, + { 232, 1 }, + { 232, 1 }, + { 232, 2 }, + { 232, 0 }, + { 233, 1 }, + { 233, 1 }, + { 233, 1 }, + { 233, 3 }, + { 234, 0 }, + { 234, 3 }, + { 234, 3 }, + { 235, 0 }, + { 235, 2 }, + { 236, 3 }, + { 236, 2 }, + { 237, 1 }, + { 237, 1 }, + { 237, 1 }, + { 237, 1 }, + { 227, 1 }, + { 148, 3 }, + { 148, 5 }, + { 148, 3 }, + { 148, 6 }, + { 239, 0 }, + { 239, 2 }, + { 238, 1 }, + { 238, 0 }, + { 148, 3 }, +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + sqlite2_parseARG_FETCH; + SqliteStatement* objectForTokens = 0; + QStringList noTokenInheritanceFields; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + /* Silence complaints from purify about yygotominor being uninitialized + ** in some cases when it is copied into the stack after the following + ** switch. yygotominor is uninitialized when a rule reduces that does + ** not set the value of its left-hand side nonterminal. Leaving the + ** value of the nonterminal uninitialized is utterly harmless as long + ** as the value is never used. So really the only thing this code + ** accomplishes is to quieten purify. + ** + ** 2007-01-16: The wireshark project (www.wireshark.org) reports that + ** without this code, their parser segfaults. I'm not sure what there + ** parser is doing to make this happen. This is the second bug report + ** from wireshark this week. Clearly they are stressing Lemon in ways + ** that it has not been previously stressed... (SQLite ticket #2172) + */ + /*memset(&yygotominor, 0, sizeof(yygotominor));*/ + yygotominor = yyzerominor; + + + if (parserContext->executeRules) + { + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ + case 1: /* cmdlist ::= cmdlist ecmd */ +{parserContext->addQuery(yymsp[0].minor.yy203); DONT_INHERIT_TOKENS("cmdlist");} + break; + case 2: /* cmdlist ::= ecmd */ +{parserContext->addQuery(yymsp[0].minor.yy203);} + break; + case 3: /* ecmd ::= SEMI */ +{yygotominor.yy203 = new SqliteEmptyQuery();} + break; + case 4: /* ecmd ::= explain cmdx SEMI */ +{ + yygotominor.yy203 = yymsp[-1].minor.yy203; + yygotominor.yy203->explain = yymsp[-2].minor.yy91->explain; + yygotominor.yy203->queryPlan = yymsp[-2].minor.yy91->queryPlan; + delete yymsp[-2].minor.yy91; + objectForTokens = yygotominor.yy203; + } + break; + case 5: /* explain ::= */ +{yygotominor.yy91 = new ParserStubExplain(false, false);} + break; + case 6: /* explain ::= EXPLAIN */ +{yygotominor.yy91 = new ParserStubExplain(true, false);} + break; + case 7: /* cmdx ::= cmd */ + case 338: /* trigger_cmd ::= update_stmt */ yytestcase(yyruleno==338); + case 339: /* trigger_cmd ::= insert_stmt */ yytestcase(yyruleno==339); + case 340: /* trigger_cmd ::= delete_stmt */ yytestcase(yyruleno==340); + case 341: /* trigger_cmd ::= select_stmt */ yytestcase(yyruleno==341); +{yygotominor.yy203 = yymsp[0].minor.yy203;} + break; + case 8: /* cmd ::= BEGIN trans_opt onconf */ +{ + yygotominor.yy203 = new SqliteBeginTrans( + yymsp[-1].minor.yy404->transactionKw, + yymsp[-1].minor.yy404->name, + *(yymsp[0].minor.yy418) + ); + delete yymsp[-1].minor.yy404; + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy203; + } + break; + case 9: /* trans_opt ::= */ +{yygotominor.yy404 = new ParserStubTransDetails();} + break; + case 10: /* trans_opt ::= TRANSACTION */ +{ + yygotominor.yy404 = new ParserStubTransDetails(); + yygotominor.yy404->transactionKw = true; + } + break; + case 11: /* trans_opt ::= TRANSACTION nm */ + case 12: /* trans_opt ::= TRANSACTION ID_TRANS */ yytestcase(yyruleno==12); +{ + yygotominor.yy404 = new ParserStubTransDetails(); + yygotominor.yy404->transactionKw = true; + yygotominor.yy404->name = *(yymsp[0].minor.yy319); + delete yymsp[0].minor.yy319; + } + break; + case 13: /* cmd ::= COMMIT trans_opt */ +{ + yygotominor.yy203 = new SqliteCommitTrans( + yymsp[0].minor.yy404->transactionKw, + yymsp[0].minor.yy404->name, + false + ); + delete yymsp[0].minor.yy404; + objectForTokens = yygotominor.yy203; + } + break; + case 14: /* cmd ::= END trans_opt */ +{ + yygotominor.yy203 = new SqliteCommitTrans( + yymsp[0].minor.yy404->transactionKw, + yymsp[0].minor.yy404->name, + true + ); + delete yymsp[0].minor.yy404; + objectForTokens = yygotominor.yy203; + } + break; + case 15: /* cmd ::= ROLLBACK trans_opt */ +{ + yygotominor.yy203 = new SqliteRollback( + yymsp[0].minor.yy404->transactionKw, + yymsp[0].minor.yy404->name + ); + delete yymsp[0].minor.yy404; + objectForTokens = yygotominor.yy203; + } + break; + case 16: /* cmd ::= CREATE temp TABLE fullname LP columnlist conslist_opt RP */ +{ + yygotominor.yy203 = new SqliteCreateTable( + *(yymsp[-6].minor.yy226), + false, + yymsp[-4].minor.yy120->name1, + yymsp[-4].minor.yy120->name2, + *(yymsp[-2].minor.yy42), + *(yymsp[-1].minor.yy13) + ); + delete yymsp[-6].minor.yy226; + delete yymsp[-2].minor.yy42; + delete yymsp[-1].minor.yy13; + delete yymsp[-4].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 17: /* cmd ::= CREATE temp TABLE fullname AS select */ +{ + yygotominor.yy203 = new SqliteCreateTable( + *(yymsp[-4].minor.yy226), + false, + yymsp[-2].minor.yy120->name1, + yymsp[-2].minor.yy120->name2, + yymsp[0].minor.yy153 + ); + delete yymsp[-4].minor.yy226; + delete yymsp[-2].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 18: /* cmd ::= CREATE temp TABLE nm DOT ID_TAB_NEW */ +{ yy_destructor(yypParser,157,&yymsp[-4].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 19: /* cmd ::= CREATE temp TABLE ID_DB|ID_TAB_NEW */ + case 110: /* cmd ::= CREATE temp VIEW ID_VIEW_NEW */ yytestcase(yyruleno==110); + case 322: /* cmd ::= CREATE temp TRIGGER ID_TRIG_NEW */ yytestcase(yyruleno==322); +{ yy_destructor(yypParser,157,&yymsp[-2].minor); +} + break; + case 20: /* temp ::= TEMP */ +{yygotominor.yy226 = new int( (yymsp[0].minor.yy0->value.length() > 4) ? 2 : 1 );} + break; + case 21: /* temp ::= */ + case 124: /* distinct ::= */ yytestcase(yyruleno==124); +{yygotominor.yy226 = new int(0);} + break; + case 22: /* columnlist ::= columnlist COMMA column */ +{ + yymsp[-2].minor.yy42->append(yymsp[0].minor.yy147); + yygotominor.yy42 = yymsp[-2].minor.yy42; + DONT_INHERIT_TOKENS("columnlist"); + } + break; + case 23: /* columnlist ::= column */ +{ + yygotominor.yy42 = new ParserCreateTableColumnList(); + yygotominor.yy42->append(yymsp[0].minor.yy147); + } + break; + case 24: /* column ::= columnid type carglist */ +{ + yygotominor.yy147 = new SqliteCreateTable::Column(*(yymsp[-2].minor.yy319), yymsp[-1].minor.yy57, *(yymsp[0].minor.yy371)); + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy371; + objectForTokens = yygotominor.yy147; + } + break; + case 25: /* columnid ::= nm */ + case 26: /* columnid ::= ID_COL_NEW */ yytestcase(yyruleno==26); + case 29: /* nm ::= id */ yytestcase(yyruleno==29); + case 37: /* typename ::= ids */ yytestcase(yyruleno==37); + case 156: /* dbnm ::= DOT nm */ yytestcase(yyruleno==156); + case 169: /* collate ::= COLLATE id */ yytestcase(yyruleno==169); +{yygotominor.yy319 = yymsp[0].minor.yy319;} + break; + case 27: /* id ::= ID */ +{ + yygotominor.yy319 = new QString( + stripObjName( + yymsp[0].minor.yy0->value, + parserContext->dialect + ) + ); + } + break; + case 28: /* ids ::= ID|STRING */ + case 31: /* nm ::= JOIN_KW */ yytestcase(yyruleno==31); +{yygotominor.yy319 = new QString(yymsp[0].minor.yy0->value);} + break; + case 30: /* nm ::= STRING */ +{yygotominor.yy319 = new QString(stripString(yymsp[0].minor.yy0->value));} + break; + case 32: /* type ::= */ +{yygotominor.yy57 = nullptr;} + break; + case 33: /* type ::= typetoken */ +{yygotominor.yy57 = yymsp[0].minor.yy57;} + break; + case 34: /* typetoken ::= typename */ +{ + yygotominor.yy57 = new SqliteColumnType(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy57; + } + break; + case 35: /* typetoken ::= typename LP signed RP */ +{ + yygotominor.yy57 = new SqliteColumnType(*(yymsp[-3].minor.yy319), *(yymsp[-1].minor.yy69)); + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy69; + objectForTokens = yygotominor.yy57; + } + break; + case 36: /* typetoken ::= typename LP signed COMMA signed RP */ +{ + yygotominor.yy57 = new SqliteColumnType(*(yymsp[-5].minor.yy319), *(yymsp[-3].minor.yy69), *(yymsp[-1].minor.yy69)); + delete yymsp[-5].minor.yy319; + delete yymsp[-3].minor.yy69; + delete yymsp[-1].minor.yy69; + objectForTokens = yygotominor.yy57; + } + break; + case 38: /* typename ::= typename ids */ + case 39: /* typename ::= ID_COL_TYPE */ yytestcase(yyruleno==39); +{ + yymsp[-1].minor.yy319->append(" " + *(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + yygotominor.yy319 = yymsp[-1].minor.yy319; + } + break; + case 40: /* signed ::= plus_num */ + case 41: /* signed ::= minus_num */ yytestcase(yyruleno==41); + case 307: /* nmnum ::= plus_num */ yytestcase(yyruleno==307); + case 312: /* plus_num ::= PLUS number */ yytestcase(yyruleno==312); + case 313: /* plus_num ::= number */ yytestcase(yyruleno==313); +{yygotominor.yy69 = yymsp[0].minor.yy69;} + break; + case 42: /* carglist ::= carglist ccons */ + case 44: /* carglist ::= carglist carg */ yytestcase(yyruleno==44); +{ + yymsp[-1].minor.yy371->append(yymsp[0].minor.yy304); + yygotominor.yy371 = yymsp[-1].minor.yy371; + DONT_INHERIT_TOKENS("carglist"); + } + break; + case 43: /* carglist ::= carglist ccons_nm ccons */ +{ + yymsp[-2].minor.yy371->append(yymsp[-1].minor.yy304); + yymsp[-2].minor.yy371->append(yymsp[0].minor.yy304); + yygotominor.yy371 = yymsp[-2].minor.yy371; + DONT_INHERIT_TOKENS("carglist"); + } + break; + case 45: /* carglist ::= */ +{yygotominor.yy371 = new ParserCreateTableColumnConstraintList();} + break; + case 46: /* carg ::= DEFAULT STRING */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefId(stripObjName( + yymsp[0].minor.yy0->value, + parserContext->dialect + )); + objectForTokens = yygotominor.yy304; + } + break; + case 47: /* carg ::= DEFAULT ID */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefId(stripObjName( + yymsp[0].minor.yy0->value, + parserContext->dialect + )); + objectForTokens = yygotominor.yy304; + + } + break; + case 48: /* carg ::= DEFAULT INTEGER */ + case 49: /* carg ::= DEFAULT PLUS INTEGER */ yytestcase(yyruleno==49); +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toLongLong(); + yygotominor.yy304->initDefTerm(val, false); + objectForTokens = yygotominor.yy304; + } + break; + case 50: /* carg ::= DEFAULT MINUS INTEGER */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toLongLong(); + yygotominor.yy304->initDefTerm(val, true); + objectForTokens = yygotominor.yy304; + } + break; + case 51: /* carg ::= DEFAULT FLOAT */ + case 52: /* carg ::= DEFAULT PLUS FLOAT */ yytestcase(yyruleno==52); +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toDouble(); + yygotominor.yy304->initDefTerm(val, false); + objectForTokens = yygotominor.yy304; + } + break; + case 53: /* carg ::= DEFAULT MINUS FLOAT */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toDouble(); + yygotominor.yy304->initDefTerm(val, true); + objectForTokens = yygotominor.yy304; + } + break; + case 54: /* carg ::= DEFAULT NULL */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefTerm(QVariant(), false); + objectForTokens = yygotominor.yy304; + } + break; + case 55: /* ccons_nm ::= CONSTRAINT nm */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefNameOnly(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy304; + } + break; + case 56: /* ccons ::= NULL onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initNull(*(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 57: /* ccons ::= NOT NULL onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initNotNull(*(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 58: /* ccons ::= PRIMARY KEY sortorder onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initPk(*(yymsp[-1].minor.yy389), *(yymsp[0].minor.yy418), false); + delete yymsp[-1].minor.yy389; + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 59: /* ccons ::= UNIQUE onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initUnique(*(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 60: /* ccons ::= CHECK LP expr RP onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initCheck(yymsp[-2].minor.yy192, *(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 61: /* ccons ::= REFERENCES nm idxlist_opt refargs */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initFk(*(yymsp[-2].minor.yy319), *(yymsp[-1].minor.yy63), *(yymsp[0].minor.yy264)); + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy264; + delete yymsp[-1].minor.yy63; + objectForTokens = yygotominor.yy304; + } + break; + case 62: /* ccons ::= defer_subclause */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefer(yymsp[0].minor.yy329->initially, yymsp[0].minor.yy329->deferrable); + delete yymsp[0].minor.yy329; + objectForTokens = yygotominor.yy304; + } + break; + case 63: /* ccons ::= COLLATE id */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initColl(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy304; + } + break; + case 64: /* ccons ::= CHECK LP RP */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initCheck(); + objectForTokens = yygotominor.yy304; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 65: /* refargs ::= */ +{yygotominor.yy264 = new ParserFkConditionList();} + break; + case 66: /* refargs ::= refargs refarg */ +{ + yymsp[-1].minor.yy264->append(yymsp[0].minor.yy187); + yygotominor.yy264 = yymsp[-1].minor.yy264; + DONT_INHERIT_TOKENS("refargs"); + } + break; + case 67: /* refarg ::= MATCH nm */ +{ + yygotominor.yy187 = new SqliteForeignKey::Condition(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + } + break; + case 68: /* refarg ::= ON INSERT refact */ +{yygotominor.yy187 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::INSERT, *(yymsp[0].minor.yy424)); delete yymsp[0].minor.yy424;} + break; + case 69: /* refarg ::= ON DELETE refact */ +{yygotominor.yy187 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::DELETE, *(yymsp[0].minor.yy424)); delete yymsp[0].minor.yy424;} + break; + case 70: /* refarg ::= ON UPDATE refact */ + case 71: /* refarg ::= MATCH ID_FK_MATCH */ yytestcase(yyruleno==71); +{yygotominor.yy187 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::UPDATE, *(yymsp[0].minor.yy424)); delete yymsp[0].minor.yy424;} + break; + case 72: /* refact ::= SET NULL */ +{yygotominor.yy424 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_NULL);} + break; + case 73: /* refact ::= SET DEFAULT */ +{yygotominor.yy424 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_DEFAULT);} + break; + case 74: /* refact ::= CASCADE */ +{yygotominor.yy424 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::CASCADE);} + break; + case 75: /* refact ::= RESTRICT */ +{yygotominor.yy424 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::RESTRICT);} + break; + case 76: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ +{ + yygotominor.yy329 = new ParserDeferSubClause(SqliteDeferrable::NOT_DEFERRABLE, *(yymsp[0].minor.yy312)); + delete yymsp[0].minor.yy312; + } + break; + case 77: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ +{ + yygotominor.yy329 = new ParserDeferSubClause(SqliteDeferrable::DEFERRABLE, *(yymsp[0].minor.yy312)); + delete yymsp[0].minor.yy312; + } + break; + case 78: /* init_deferred_pred_opt ::= */ +{yygotominor.yy312 = new SqliteInitially(SqliteInitially::null);} + break; + case 79: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ +{yygotominor.yy312 = new SqliteInitially(SqliteInitially::DEFERRED);} + break; + case 80: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ +{yygotominor.yy312 = new SqliteInitially(SqliteInitially::IMMEDIATE);} + break; + case 81: /* conslist_opt ::= */ +{yygotominor.yy13 = new ParserCreateTableConstraintList();} + break; + case 82: /* conslist_opt ::= COMMA conslist */ +{yygotominor.yy13 = yymsp[0].minor.yy13;} + break; + case 83: /* conslist ::= conslist tconscomma tcons */ +{ + yymsp[0].minor.yy406->afterComma = *(yymsp[-1].minor.yy291); + yymsp[-2].minor.yy13->append(yymsp[0].minor.yy406); + yygotominor.yy13 = yymsp[-2].minor.yy13; + delete yymsp[-1].minor.yy291; + DONT_INHERIT_TOKENS("conslist"); + } + break; + case 84: /* conslist ::= tcons */ +{ + yygotominor.yy13 = new ParserCreateTableConstraintList(); + yygotominor.yy13->append(yymsp[0].minor.yy406); + } + break; + case 85: /* tconscomma ::= COMMA */ + case 269: /* not_opt ::= NOT */ yytestcase(yyruleno==269); + case 285: /* uniqueflag ::= UNIQUE */ yytestcase(yyruleno==285); + case 349: /* database_kw_opt ::= DATABASE */ yytestcase(yyruleno==349); +{yygotominor.yy291 = new bool(true);} + break; + case 86: /* tconscomma ::= */ + case 268: /* not_opt ::= */ yytestcase(yyruleno==268); + case 286: /* uniqueflag ::= */ yytestcase(yyruleno==286); + case 350: /* database_kw_opt ::= */ yytestcase(yyruleno==350); +{yygotominor.yy291 = new bool(false);} + break; + case 87: /* tcons ::= CONSTRAINT nm */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initNameOnly(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy406; + } + break; + case 88: /* tcons ::= PRIMARY KEY LP idxlist RP onconf */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initPk(*(yymsp[-2].minor.yy63), false, *(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + delete yymsp[-2].minor.yy63; + objectForTokens = yygotominor.yy406; + } + break; + case 89: /* tcons ::= UNIQUE LP idxlist RP onconf */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initUnique(*(yymsp[-2].minor.yy63), *(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + delete yymsp[-2].minor.yy63; + objectForTokens = yygotominor.yy406; + } + break; + case 90: /* tcons ::= CHECK LP expr RP onconf */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initCheck(yymsp[-2].minor.yy192, *(yymsp[0].minor.yy418)); + objectForTokens = yygotominor.yy406; + } + break; + case 91: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt */ + case 92: /* tcons ::= CONSTRAINT ID_CONSTR */ yytestcase(yyruleno==92); + case 93: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES ID_TAB */ yytestcase(yyruleno==93); +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initFk( + *(yymsp[-6].minor.yy63), + *(yymsp[-3].minor.yy319), + *(yymsp[-2].minor.yy63), + *(yymsp[-1].minor.yy264), + yymsp[0].minor.yy329->initially, + yymsp[0].minor.yy329->deferrable + ); + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy264; + delete yymsp[0].minor.yy329; + delete yymsp[-2].minor.yy63; + delete yymsp[-6].minor.yy63; + objectForTokens = yygotominor.yy406; + } + break; + case 94: /* tcons ::= CHECK LP RP onconf */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initCheck(); + objectForTokens = yygotominor.yy406; + parserContext->minorErrorAfterLastToken("Syntax error"); + yy_destructor(yypParser,155,&yymsp[0].minor); +} + break; + case 95: /* defer_subclause_opt ::= */ +{yygotominor.yy329 = new ParserDeferSubClause(SqliteDeferrable::null, SqliteInitially::null);} + break; + case 96: /* defer_subclause_opt ::= defer_subclause */ +{yygotominor.yy329 = yymsp[0].minor.yy329;} + break; + case 97: /* onconf ::= */ + case 99: /* orconf ::= */ yytestcase(yyruleno==99); +{yygotominor.yy418 = new SqliteConflictAlgo(SqliteConflictAlgo::null);} + break; + case 98: /* onconf ::= ON CONFLICT resolvetype */ + case 100: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==100); +{yygotominor.yy418 = yymsp[0].minor.yy418;} + break; + case 101: /* resolvetype ::= ROLLBACK */ + case 102: /* resolvetype ::= ABORT */ yytestcase(yyruleno==102); + case 103: /* resolvetype ::= FAIL */ yytestcase(yyruleno==103); + case 104: /* resolvetype ::= IGNORE */ yytestcase(yyruleno==104); + case 105: /* resolvetype ::= REPLACE */ yytestcase(yyruleno==105); +{yygotominor.yy418 = new SqliteConflictAlgo(sqliteConflictAlgo(yymsp[0].minor.yy0->value));} + break; + case 106: /* cmd ::= DROP TABLE fullname */ +{ + yygotominor.yy203 = new SqliteDropTable(false, yymsp[0].minor.yy120->name1, yymsp[0].minor.yy120->name2); + delete yymsp[0].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 107: /* cmd ::= DROP TABLE nm DOT ID_TAB */ + case 108: /* cmd ::= DROP TABLE ID_DB|ID_TAB */ yytestcase(yyruleno==108); + case 148: /* singlesrc ::= nm DOT ID_TAB */ yytestcase(yyruleno==148); + case 149: /* singlesrc ::= ID_DB|ID_TAB */ yytestcase(yyruleno==149); + case 150: /* singlesrc ::= nm DOT ID_VIEW */ yytestcase(yyruleno==150); + case 151: /* singlesrc ::= ID_DB|ID_VIEW */ yytestcase(yyruleno==151); + case 186: /* delete_stmt ::= DELETE FROM nm DOT ID_TAB */ yytestcase(yyruleno==186); + case 187: /* delete_stmt ::= DELETE FROM ID_DB|ID_TAB */ yytestcase(yyruleno==187); + case 195: /* update_stmt ::= UPDATE orconf nm DOT ID_TAB */ yytestcase(yyruleno==195); + case 196: /* update_stmt ::= UPDATE orconf ID_DB|ID_TAB */ yytestcase(yyruleno==196); + case 263: /* exprx ::= nm DOT ID_TAB|ID_COL */ yytestcase(yyruleno==263); + case 283: /* cmd ::= CREATE uniqueflag INDEX nm DOT ID_IDX_NEW */ yytestcase(yyruleno==283); + case 284: /* cmd ::= CREATE uniqueflag INDEX ID_DB|ID_IDX_NEW */ yytestcase(yyruleno==284); + case 294: /* cmd ::= DROP INDEX nm DOT ID_IDX */ yytestcase(yyruleno==294); + case 295: /* cmd ::= DROP INDEX ID_DB|ID_IDX */ yytestcase(yyruleno==295); + case 305: /* cmd ::= PRAGMA nm DOT ID_PRAGMA */ yytestcase(yyruleno==305); + case 306: /* cmd ::= PRAGMA ID_DB|ID_PRAGMA */ yytestcase(yyruleno==306); + case 344: /* cmd ::= DROP TRIGGER nm DOT ID_TRIG */ yytestcase(yyruleno==344); + case 345: /* cmd ::= DROP TRIGGER ID_DB|ID_TRIG */ yytestcase(yyruleno==345); +{ yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 109: /* cmd ::= CREATE temp VIEW nm AS select */ +{ + yygotominor.yy203 = new SqliteCreateView(*(yymsp[-4].minor.yy226), false, *(yymsp[-2].minor.yy319), QString::null, yymsp[0].minor.yy153); + delete yymsp[-4].minor.yy226; + delete yymsp[-2].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 111: /* cmd ::= DROP VIEW nm */ + case 112: /* cmd ::= DROP VIEW ID_VIEW */ yytestcase(yyruleno==112); +{ + yygotominor.yy203 = new SqliteDropView(false, *(yymsp[0].minor.yy319), QString::null); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 113: /* cmd ::= select_stmt */ + case 182: /* cmd ::= delete_stmt */ yytestcase(yyruleno==182); + case 191: /* cmd ::= update_stmt */ yytestcase(yyruleno==191); + case 203: /* cmd ::= insert_stmt */ yytestcase(yyruleno==203); +{ + yygotominor.yy203 = yymsp[0].minor.yy203; + objectForTokens = yygotominor.yy203; + } + break; + case 114: /* select_stmt ::= select */ +{ + yygotominor.yy203 = yymsp[0].minor.yy153; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 115: /* select ::= oneselect */ +{ + yygotominor.yy153 = SqliteSelect::append(yymsp[0].minor.yy150); + objectForTokens = yygotominor.yy153; + } + break; + case 116: /* select ::= select multiselect_op oneselect */ +{ + yygotominor.yy153 = SqliteSelect::append(yymsp[-2].minor.yy153, *(yymsp[-1].minor.yy382), yymsp[0].minor.yy150); + delete yymsp[-1].minor.yy382; + objectForTokens = yygotominor.yy153; + } + break; + case 117: /* multiselect_op ::= UNION */ +{yygotominor.yy382 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION);} + break; + case 118: /* multiselect_op ::= UNION ALL */ +{yygotominor.yy382 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION_ALL);} + break; + case 119: /* multiselect_op ::= EXCEPT */ +{yygotominor.yy382 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::EXCEPT);} + break; + case 120: /* multiselect_op ::= INTERSECT */ +{yygotominor.yy382 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::INTERSECT);} + break; + case 121: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ +{ + yygotominor.yy150 = new SqliteSelect::Core( + *(yymsp[-7].minor.yy226), + *(yymsp[-6].minor.yy213), + yymsp[-5].minor.yy31, + yymsp[-4].minor.yy192, + *(yymsp[-3].minor.yy231), + yymsp[-2].minor.yy192, + *(yymsp[-1].minor.yy243), + yymsp[0].minor.yy324 + ); + delete yymsp[-6].minor.yy213; + delete yymsp[-7].minor.yy226; + delete yymsp[-3].minor.yy231; + delete yymsp[-1].minor.yy243; + objectForTokens = yygotominor.yy150; + } + break; + case 122: /* distinct ::= DISTINCT */ +{yygotominor.yy226 = new int(1);} + break; + case 123: /* distinct ::= ALL */ +{yygotominor.yy226 = new int(2);} + break; + case 125: /* sclp ::= selcollist COMMA */ +{yygotominor.yy213 = yymsp[-1].minor.yy213;} + break; + case 126: /* sclp ::= */ +{yygotominor.yy213 = new ParserResultColumnList();} + break; + case 127: /* selcollist ::= sclp expr as */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + yymsp[-1].minor.yy192, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->asKw : false, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->name : QString::null + ); + + yymsp[-2].minor.yy213->append(obj); + yygotominor.yy213 = yymsp[-2].minor.yy213; + delete yymsp[0].minor.yy40; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 128: /* selcollist ::= sclp STAR */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn(true); + + yymsp[-1].minor.yy213->append(obj); + yygotominor.yy213 = yymsp[-1].minor.yy213; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 129: /* selcollist ::= sclp nm DOT STAR */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + true, + *(yymsp[-2].minor.yy319) + ); + yymsp[-3].minor.yy213->append(obj); + yygotominor.yy213 = yymsp[-3].minor.yy213; + delete yymsp[-2].minor.yy319; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 130: /* selcollist ::= sclp */ + case 131: /* selcollist ::= sclp ID_TAB DOT STAR */ yytestcase(yyruleno==131); +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy213 = yymsp[0].minor.yy213; + } + break; + case 132: /* as ::= AS nm */ +{ + yygotominor.yy40 = new ParserStubAlias(*(yymsp[0].minor.yy319), true); + delete yymsp[0].minor.yy319; + } + break; + case 133: /* as ::= ids */ + case 134: /* as ::= AS ID_ALIAS */ yytestcase(yyruleno==134); + case 135: /* as ::= ID_ALIAS */ yytestcase(yyruleno==135); +{ + yygotominor.yy40 = new ParserStubAlias(*(yymsp[0].minor.yy319), false); + delete yymsp[0].minor.yy319; + } + break; + case 136: /* as ::= */ +{yygotominor.yy40 = nullptr;} + break; + case 137: /* from ::= */ +{yygotominor.yy31 = nullptr;} + break; + case 138: /* from ::= FROM joinsrc */ +{yygotominor.yy31 = yymsp[0].minor.yy31;} + break; + case 139: /* joinsrc ::= singlesrc seltablist */ +{ + yygotominor.yy31 = new SqliteSelect::Core::JoinSource( + yymsp[-1].minor.yy121, + *(yymsp[0].minor.yy131) + ); + delete yymsp[0].minor.yy131; + objectForTokens = yygotominor.yy31; + } + break; + case 140: /* joinsrc ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy31 = new SqliteSelect::Core::JoinSource(); + objectForTokens = yygotominor.yy31; + } + break; + case 141: /* seltablist ::= seltablist joinop singlesrc joinconstr_opt */ +{ + SqliteSelect::Core::JoinSourceOther* src = + new SqliteSelect::Core::JoinSourceOther(yymsp[-2].minor.yy221, yymsp[-1].minor.yy121, yymsp[0].minor.yy455); + + yymsp[-3].minor.yy131->append(src); + yygotominor.yy131 = yymsp[-3].minor.yy131; + objectForTokens = src; + DONT_INHERIT_TOKENS("seltablist"); + } + break; + case 142: /* seltablist ::= */ +{ + yygotominor.yy131 = new ParserOtherSourceList(); + } + break; + case 143: /* singlesrc ::= nm dbnm as */ +{ + yygotominor.yy121 = new SqliteSelect::Core::SingleSource( + *(yymsp[-2].minor.yy319), + *(yymsp[-1].minor.yy319), + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->asKw : false, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->name : QString::null, + false, + QString::null + ); + delete yymsp[-2].minor.yy319; + delete yymsp[-1].minor.yy319; + delete yymsp[0].minor.yy40; + objectForTokens = yygotominor.yy121; + } + break; + case 144: /* singlesrc ::= LP select RP as */ +{ + yygotominor.yy121 = new SqliteSelect::Core::SingleSource( + yymsp[-2].minor.yy153, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->asKw : false, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->name : QString::null + ); + delete yymsp[0].minor.yy40; + objectForTokens = yygotominor.yy121; + } + break; + case 145: /* singlesrc ::= LP joinsrc RP as */ +{ + yygotominor.yy121 = new SqliteSelect::Core::SingleSource( + yymsp[-2].minor.yy31, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->asKw : false, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->name : QString::null + ); + delete yymsp[0].minor.yy40; + objectForTokens = yygotominor.yy121; + } + break; + case 146: /* singlesrc ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy121 = new SqliteSelect::Core::SingleSource(); + objectForTokens = yygotominor.yy121; + } + break; + case 147: /* singlesrc ::= nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy121 = new SqliteSelect::Core::SingleSource(); + yygotominor.yy121->database = *(yymsp[-1].minor.yy319); + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy121; + } + break; + case 152: /* joinconstr_opt ::= ON expr */ +{ + yygotominor.yy455 = new SqliteSelect::Core::JoinConstraint(yymsp[0].minor.yy192); + objectForTokens = yygotominor.yy455; + } + break; + case 153: /* joinconstr_opt ::= USING LP inscollist RP */ +{ + yygotominor.yy455 = new SqliteSelect::Core::JoinConstraint(*(yymsp[-1].minor.yy207)); + delete yymsp[-1].minor.yy207; + objectForTokens = yygotominor.yy455; + } + break; + case 154: /* joinconstr_opt ::= */ +{yygotominor.yy455 = nullptr;} + break; + case 155: /* dbnm ::= */ +{yygotominor.yy319 = new QString();} + break; + case 157: /* fullname ::= nm dbnm */ +{ + yygotominor.yy120 = new ParserFullName(); + yygotominor.yy120->name1 = *(yymsp[-1].minor.yy319); + yygotominor.yy120->name2 = *(yymsp[0].minor.yy319); + delete yymsp[-1].minor.yy319; + delete yymsp[0].minor.yy319; + } + break; + case 158: /* joinop ::= COMMA */ +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(true); + objectForTokens = yygotominor.yy221; + } + break; + case 159: /* joinop ::= JOIN */ +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(false); + objectForTokens = yygotominor.yy221; + } + break; + case 160: /* joinop ::= JOIN_KW JOIN */ +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy221; + } + break; + case 161: /* joinop ::= JOIN_KW nm JOIN */ +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(yymsp[-2].minor.yy0->value, *(yymsp[-1].minor.yy319)); + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy221; + } + break; + case 162: /* joinop ::= JOIN_KW nm nm JOIN */ + case 163: /* joinop ::= ID_JOIN_OPTS */ yytestcase(yyruleno==163); +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(yymsp[-3].minor.yy0->value, *(yymsp[-2].minor.yy319), *(yymsp[-1].minor.yy319)); + delete yymsp[-2].minor.yy319; + delete yymsp[-2].minor.yy319; + objectForTokens = yygotominor.yy221; + } + break; + case 164: /* orderby_opt ::= */ +{yygotominor.yy243 = new ParserOrderByList();} + break; + case 165: /* orderby_opt ::= ORDER BY sortlist */ +{yygotominor.yy243 = yymsp[0].minor.yy243;} + break; + case 166: /* sortlist ::= sortlist COMMA collate expr sortorder */ +{ + SqliteOrderBy* obj; + if (yymsp[-2].minor.yy319) + { + SqliteExpr* coll = new SqliteExpr(); + coll->initCollate(yymsp[-1].minor.yy192, *(yymsp[-2].minor.yy319)); + delete yymsp[-2].minor.yy319; + obj = new SqliteOrderBy(coll, *(yymsp[0].minor.yy389)); + } + else + { + obj = new SqliteOrderBy(yymsp[-1].minor.yy192, *(yymsp[0].minor.yy389)); + } + yymsp[-4].minor.yy243->append(obj); + yygotominor.yy243 = yymsp[-4].minor.yy243; + delete yymsp[0].minor.yy389; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sortlist"); + } + break; + case 167: /* sortlist ::= expr collate sortorder */ +{ + SqliteOrderBy* obj; + if (yymsp[-1].minor.yy319) + { + SqliteExpr* coll = new SqliteExpr(); + coll->initCollate(yymsp[-2].minor.yy192, *(yymsp[-1].minor.yy319)); + delete yymsp[-1].minor.yy319; + obj = new SqliteOrderBy(coll, *(yymsp[0].minor.yy389)); + } + else + { + obj = new SqliteOrderBy(yymsp[-2].minor.yy192, *(yymsp[0].minor.yy389)); + } + yygotominor.yy243 = new ParserOrderByList(); + yygotominor.yy243->append(obj); + delete yymsp[0].minor.yy389; + objectForTokens = obj; + } + break; + case 168: /* collate ::= */ +{yygotominor.yy319 = nullptr;} + break; + case 170: /* sortorder ::= ASC */ +{yygotominor.yy389 = new SqliteSortOrder(SqliteSortOrder::ASC);} + break; + case 171: /* sortorder ::= DESC */ +{yygotominor.yy389 = new SqliteSortOrder(SqliteSortOrder::DESC);} + break; + case 172: /* sortorder ::= */ +{yygotominor.yy389 = new SqliteSortOrder(SqliteSortOrder::null);} + break; + case 173: /* groupby_opt ::= */ + case 278: /* exprlist ::= */ yytestcase(yyruleno==278); +{yygotominor.yy231 = new ParserExprList();} + break; + case 174: /* groupby_opt ::= GROUP BY nexprlist */ + case 277: /* exprlist ::= nexprlist */ yytestcase(yyruleno==277); +{yygotominor.yy231 = yymsp[0].minor.yy231;} + break; + case 175: /* groupby_opt ::= GROUP BY */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy231 = new ParserExprList(); + } + break; + case 176: /* having_opt ::= */ + case 188: /* where_opt ::= */ yytestcase(yyruleno==188); + case 274: /* case_else ::= */ yytestcase(yyruleno==274); + case 276: /* case_operand ::= */ yytestcase(yyruleno==276); + case 334: /* when_clause ::= */ yytestcase(yyruleno==334); + case 347: /* key_opt ::= */ yytestcase(yyruleno==347); +{yygotominor.yy192 = nullptr;} + break; + case 177: /* having_opt ::= HAVING expr */ + case 189: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==189); + case 266: /* expr ::= exprx */ yytestcase(yyruleno==266); + case 273: /* case_else ::= ELSE expr */ yytestcase(yyruleno==273); + case 275: /* case_operand ::= exprx */ yytestcase(yyruleno==275); + case 335: /* when_clause ::= WHEN expr */ yytestcase(yyruleno==335); +{yygotominor.yy192 = yymsp[0].minor.yy192;} + break; + case 178: /* limit_opt ::= */ +{yygotominor.yy324 = nullptr;} + break; + case 179: /* limit_opt ::= LIMIT signed */ +{ + yygotominor.yy324 = new SqliteLimit(*(yymsp[0].minor.yy69)); + delete yymsp[0].minor.yy69; + objectForTokens = yygotominor.yy324; + } + break; + case 180: /* limit_opt ::= LIMIT signed OFFSET signed */ +{ + SqliteExpr* expr1 = new SqliteExpr(); + expr1->initLiteral(*(yymsp[-2].minor.yy69)); + expr1->setParent(yygotominor.yy324); + + SqliteExpr* expr2 = new SqliteExpr(); + expr1->initLiteral(*(yymsp[0].minor.yy69)); + expr1->setParent(yygotominor.yy324); + + yygotominor.yy324 = new SqliteLimit(expr1, expr2, true); + + TokenPtr limitToken = TokenPtr::create(Token::INTEGER, yymsp[-2].minor.yy69->toString()); + parserContext->addManagedToken(limitToken); + expr1->tokens << limitToken; + expr1->tokensMap["term"] << limitToken; + + TokenPtr offsetToken = TokenPtr::create(Token::INTEGER, yymsp[0].minor.yy69->toString()); + parserContext->addManagedToken(offsetToken); + expr2->tokens << offsetToken; + expr2->tokensMap["term"] << offsetToken; + + delete yymsp[-2].minor.yy69; + delete yymsp[0].minor.yy69; + objectForTokens = yygotominor.yy324; + } + break; + case 181: /* limit_opt ::= LIMIT signed COMMA signed */ +{ + SqliteExpr* expr1 = new SqliteExpr(); + expr1->initLiteral(*(yymsp[-2].minor.yy69)); + expr1->setParent(yygotominor.yy324); + + SqliteExpr* expr2 = new SqliteExpr(); + expr1->initLiteral(*(yymsp[0].minor.yy69)); + expr1->setParent(yygotominor.yy324); + + yygotominor.yy324 = new SqliteLimit(expr1, expr2, false); + + TokenPtr limitToken = TokenPtr::create(Token::INTEGER, yymsp[-2].minor.yy69->toString()); + parserContext->addManagedToken(limitToken); + expr1->tokens << limitToken; + expr1->tokensMap["term"] << limitToken; + + TokenPtr offsetToken = TokenPtr::create(Token::INTEGER, yymsp[0].minor.yy69->toString()); + parserContext->addManagedToken(offsetToken); + expr2->tokens << offsetToken; + expr2->tokensMap["term"] << offsetToken; + + delete yymsp[-2].minor.yy69; + delete yymsp[0].minor.yy69; + objectForTokens = yygotominor.yy324; + } + break; + case 183: /* delete_stmt ::= DELETE FROM fullname where_opt */ +{ + yygotominor.yy203 = new SqliteDelete( + yymsp[-1].minor.yy120->name1, + yymsp[-1].minor.yy120->name2, + false, + yymsp[0].minor.yy192, + nullptr + ); + delete yymsp[-1].minor.yy120; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 184: /* delete_stmt ::= DELETE FROM */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + } + break; + case 185: /* delete_stmt ::= DELETE FROM nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->database = *(yymsp[-1].minor.yy319); + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + delete yymsp[-1].minor.yy319; + } + break; + case 190: /* where_opt ::= WHERE */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy192 = new SqliteExpr(); + } + break; + case 192: /* update_stmt ::= UPDATE orconf fullname SET setlist where_opt */ +{ + yygotominor.yy203 = new SqliteUpdate( + *(yymsp[-4].minor.yy418), + yymsp[-3].minor.yy120->name1, + yymsp[-3].minor.yy120->name2, + false, + QString::null, + *(yymsp[-1].minor.yy201), + yymsp[0].minor.yy192, + nullptr + ); + delete yymsp[-4].minor.yy418; + delete yymsp[-3].minor.yy120; + delete yymsp[-1].minor.yy201; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 193: /* update_stmt ::= UPDATE orconf */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy203 = new SqliteUpdate(); + objectForTokens = yygotominor.yy203; + delete yymsp[0].minor.yy418; + } + break; + case 194: /* update_stmt ::= UPDATE orconf nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->database = *(yymsp[-1].minor.yy319); + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + delete yymsp[-2].minor.yy418; + delete yymsp[-1].minor.yy319; + } + break; + case 197: /* setlist ::= setlist COMMA nm EQ expr */ +{ + yymsp[-4].minor.yy201->append(ParserSetValue(*(yymsp[-2].minor.yy319), yymsp[0].minor.yy192)); + yygotominor.yy201 = yymsp[-4].minor.yy201; + delete yymsp[-2].minor.yy319; + DONT_INHERIT_TOKENS("setlist"); + } + break; + case 198: /* setlist ::= nm EQ expr */ +{ + yygotominor.yy201 = new ParserSetValueList(); + yygotominor.yy201->append(ParserSetValue(*(yymsp[-2].minor.yy319), yymsp[0].minor.yy192)); + delete yymsp[-2].minor.yy319; + } + break; + case 199: /* setlist ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy201 = new ParserSetValueList(); + } + break; + case 200: /* setlist ::= setlist COMMA */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy201 = yymsp[-1].minor.yy201; + } + break; + case 201: /* setlist ::= setlist COMMA ID_COL */ + case 202: /* setlist ::= ID_COL */ yytestcase(yyruleno==202); +{ yy_destructor(yypParser,216,&yymsp[-2].minor); +} + break; + case 204: /* insert_stmt ::= insert_cmd INTO fullname inscollist_opt VALUES LP exprlist RP */ +{ + yygotominor.yy203 = new SqliteInsert( + yymsp[-7].minor.yy344->replace, + yymsp[-7].minor.yy344->orConflict, + yymsp[-5].minor.yy120->name1, + yymsp[-5].minor.yy120->name2, + *(yymsp[-4].minor.yy207), + *(yymsp[-1].minor.yy231), + nullptr + ); + delete yymsp[-5].minor.yy120; + delete yymsp[-7].minor.yy344; + delete yymsp[-1].minor.yy231; + delete yymsp[-4].minor.yy207; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 205: /* insert_stmt ::= insert_cmd INTO fullname inscollist_opt select */ +{ + yygotominor.yy203 = new SqliteInsert( + yymsp[-4].minor.yy344->replace, + yymsp[-4].minor.yy344->orConflict, + yymsp[-2].minor.yy120->name1, + yymsp[-2].minor.yy120->name2, + *(yymsp[-1].minor.yy207), + yymsp[0].minor.yy153, + nullptr + ); + delete yymsp[-2].minor.yy120; + delete yymsp[-4].minor.yy344; + delete yymsp[-1].minor.yy207; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 206: /* insert_stmt ::= insert_cmd INTO */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = yymsp[-1].minor.yy344->replace; + q->onConflict = yymsp[-1].minor.yy344->orConflict; + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + delete yymsp[-1].minor.yy344; + } + break; + case 207: /* insert_stmt ::= insert_cmd INTO nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = yymsp[-3].minor.yy344->replace; + q->onConflict = yymsp[-3].minor.yy344->orConflict; + q->database = *(yymsp[-1].minor.yy319); + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + delete yymsp[-3].minor.yy344; + delete yymsp[-1].minor.yy319; + } + break; + case 208: /* insert_stmt ::= insert_cmd INTO ID_DB|ID_TAB */ +{ yy_destructor(yypParser,218,&yymsp[-2].minor); +} + break; + case 209: /* insert_stmt ::= insert_cmd INTO nm DOT ID_TAB */ +{ yy_destructor(yypParser,218,&yymsp[-4].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 210: /* insert_cmd ::= INSERT orconf */ +{ + yygotominor.yy344 = new ParserStubInsertOrReplace(false, *(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + } + break; + case 211: /* insert_cmd ::= REPLACE */ +{yygotominor.yy344 = new ParserStubInsertOrReplace(true);} + break; + case 212: /* inscollist_opt ::= */ +{yygotominor.yy207 = new ParserStringList();} + break; + case 213: /* inscollist_opt ::= LP inscollist RP */ +{yygotominor.yy207 = yymsp[-1].minor.yy207;} + break; + case 214: /* inscollist ::= inscollist COMMA nm */ +{ + yymsp[-2].minor.yy207->append(*(yymsp[0].minor.yy319)); + yygotominor.yy207 = yymsp[-2].minor.yy207; + delete yymsp[0].minor.yy319; + DONT_INHERIT_TOKENS("inscollist"); + } + break; + case 215: /* inscollist ::= nm */ +{ + yygotominor.yy207 = new ParserStringList(); + yygotominor.yy207->append(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + } + break; + case 216: /* inscollist ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy207 = new ParserStringList(); + } + break; + case 217: /* inscollist ::= inscollist COMMA ID_COL */ + case 218: /* inscollist ::= ID_COL */ yytestcase(yyruleno==218); +{ yy_destructor(yypParser,210,&yymsp[-2].minor); +} + break; + case 219: /* exprx ::= NULL */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initNull(); + objectForTokens = yygotominor.yy192; + } + break; + case 220: /* exprx ::= INTEGER */ +{ + yygotominor.yy192 = new SqliteExpr(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toLongLong(); + yygotominor.yy192->initLiteral(val); + objectForTokens = yygotominor.yy192; + } + break; + case 221: /* exprx ::= FLOAT */ +{ + yygotominor.yy192 = new SqliteExpr(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toDouble(); + yygotominor.yy192->initLiteral(val); + objectForTokens = yygotominor.yy192; + } + break; + case 222: /* exprx ::= STRING */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initLiteral(QVariant(yymsp[0].minor.yy0->value)); + objectForTokens = yygotominor.yy192; + } + break; + case 223: /* exprx ::= LP expr RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initSubExpr(yymsp[-1].minor.yy192); + objectForTokens = yygotominor.yy192; + } + break; + case 224: /* exprx ::= id */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 225: /* exprx ::= JOIN_KW */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 226: /* exprx ::= nm DOT nm */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[-2].minor.yy319), *(yymsp[0].minor.yy319)); + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 227: /* exprx ::= nm DOT nm DOT nm */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[-4].minor.yy319), *(yymsp[-2].minor.yy319), *(yymsp[0].minor.yy319)); + delete yymsp[-4].minor.yy319; + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 228: /* exprx ::= VARIABLE */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initBindParam(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 229: /* exprx ::= ID LP exprlist RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initFunction(yymsp[-3].minor.yy0->value, false, *(yymsp[-1].minor.yy231)); + delete yymsp[-1].minor.yy231; + objectForTokens = yygotominor.yy192; + } + break; + case 230: /* exprx ::= ID LP STAR RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initFunction(yymsp[-3].minor.yy0->value, true); + objectForTokens = yygotominor.yy192; + } + break; + case 231: /* exprx ::= expr AND expr */ + case 232: /* exprx ::= expr OR expr */ yytestcase(yyruleno==232); + case 233: /* exprx ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==233); + case 234: /* exprx ::= expr EQ|NE expr */ yytestcase(yyruleno==234); + case 235: /* exprx ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==235); + case 236: /* exprx ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==236); + case 237: /* exprx ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==237); + case 238: /* exprx ::= expr CONCAT expr */ yytestcase(yyruleno==238); +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initBinOp(yymsp[-2].minor.yy192, yymsp[-1].minor.yy0->value, yymsp[0].minor.yy192); + objectForTokens = yygotominor.yy192; + } + break; + case 239: /* exprx ::= expr not_opt likeop expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initLike(yymsp[-3].minor.yy192, *(yymsp[-2].minor.yy291), *(yymsp[-1].minor.yy41), yymsp[0].minor.yy192); + delete yymsp[-2].minor.yy291; + delete yymsp[-1].minor.yy41; + objectForTokens = yygotominor.yy192; + } + break; + case 240: /* exprx ::= expr ISNULL|NOTNULL */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initNull(yymsp[-1].minor.yy192, yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 241: /* exprx ::= expr NOT NULL */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initNull(yymsp[-2].minor.yy192, "NOT NULL"); + objectForTokens = yygotominor.yy192; + } + break; + case 242: /* exprx ::= expr IS not_opt expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initIs(yymsp[-3].minor.yy192, *(yymsp[-1].minor.yy291), yymsp[0].minor.yy192); + delete yymsp[-1].minor.yy291; + objectForTokens = yygotominor.yy192; + } + break; + case 243: /* exprx ::= NOT expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initUnaryOp(yymsp[0].minor.yy192, yymsp[-1].minor.yy0->value); + } + break; + case 244: /* exprx ::= BITNOT expr */ + case 245: /* exprx ::= MINUS expr */ yytestcase(yyruleno==245); + case 246: /* exprx ::= PLUS expr */ yytestcase(yyruleno==246); +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initUnaryOp(yymsp[0].minor.yy192, yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 247: /* exprx ::= expr not_opt BETWEEN expr AND expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initBetween(yymsp[-5].minor.yy192, *(yymsp[-4].minor.yy291), yymsp[-2].minor.yy192, yymsp[0].minor.yy192); + delete yymsp[-4].minor.yy291; + objectForTokens = yygotominor.yy192; + } + break; + case 248: /* exprx ::= expr not_opt IN LP exprlist RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initIn(yymsp[-5].minor.yy192, *(yymsp[-4].minor.yy291), *(yymsp[-1].minor.yy231)); + delete yymsp[-4].minor.yy291; + delete yymsp[-1].minor.yy231; + objectForTokens = yygotominor.yy192; + } + break; + case 249: /* exprx ::= expr not_opt IN LP select RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initIn(yymsp[-5].minor.yy192, *(yymsp[-4].minor.yy291), yymsp[-1].minor.yy153); + delete yymsp[-4].minor.yy291; + objectForTokens = yygotominor.yy192; + } + break; + case 250: /* exprx ::= expr not_opt IN nm dbnm */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initIn(yymsp[-4].minor.yy192, yymsp[-3].minor.yy291, *(yymsp[-1].minor.yy319), *(yymsp[0].minor.yy319)); + delete yymsp[-3].minor.yy291; + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 251: /* exprx ::= LP select RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initSubSelect(yymsp[-1].minor.yy153); + objectForTokens = yygotominor.yy192; + } + break; + case 252: /* exprx ::= CASE case_operand case_exprlist case_else END */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initCase(yymsp[-3].minor.yy192, *(yymsp[-2].minor.yy231), yymsp[-1].minor.yy192); + delete yymsp[-2].minor.yy231; + objectForTokens = yygotominor.yy192; + } + break; + case 253: /* exprx ::= RAISE LP raisetype COMMA nm RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initRaise(yymsp[-3].minor.yy0->value, *(yymsp[-1].minor.yy319)); + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 254: /* exprx ::= RAISE LP IGNORE RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initRaise(yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 255: /* exprx ::= nm DOT */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[-1].minor.yy319), QString::null, QString::null); + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 256: /* exprx ::= nm DOT nm DOT */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[-3].minor.yy319), *(yymsp[-1].minor.yy319), QString::null); + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 257: /* exprx ::= expr not_opt BETWEEN expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + delete yymsp[-2].minor.yy291; + delete yymsp[-3].minor.yy192; + delete yymsp[0].minor.yy192; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 258: /* exprx ::= CASE case_operand case_exprlist case_else */ +{ + yygotominor.yy192 = new SqliteExpr(); + delete yymsp[-1].minor.yy231; + delete yymsp[-2].minor.yy192; + delete yymsp[0].minor.yy192; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 259: /* exprx ::= expr not_opt IN LP exprlist */ +{ + yygotominor.yy192 = new SqliteExpr(); + delete yymsp[-3].minor.yy291; + delete yymsp[0].minor.yy231; + delete yymsp[-4].minor.yy192; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 260: /* exprx ::= expr not_opt IN ID_DB */ +{ yy_destructor(yypParser,177,&yymsp[-3].minor); +} + break; + case 261: /* exprx ::= expr not_opt IN nm DOT ID_TAB */ + case 262: /* exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN */ yytestcase(yyruleno==262); +{ yy_destructor(yypParser,177,&yymsp[-5].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 264: /* exprx ::= nm DOT nm DOT ID_COL */ + case 265: /* exprx ::= RAISE LP raisetype COMMA ID_ERR_MSG RP */ yytestcase(yyruleno==265); +{ yy_destructor(yypParser,156,&yymsp[-4].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 267: /* expr ::= */ +{ + yygotominor.yy192 = new SqliteExpr(); + objectForTokens = yygotominor.yy192; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 270: /* likeop ::= LIKE|GLOB */ +{yygotominor.yy41 = new SqliteExpr::LikeOp(SqliteExpr::likeOp(yymsp[0].minor.yy0->value));} + break; + case 271: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ +{ + yymsp[-4].minor.yy231->append(yymsp[-2].minor.yy192); + yymsp[-4].minor.yy231->append(yymsp[0].minor.yy192); + yygotominor.yy231 = yymsp[-4].minor.yy231; + } + break; + case 272: /* case_exprlist ::= WHEN expr THEN expr */ +{ + yygotominor.yy231 = new ParserExprList(); + yygotominor.yy231->append(yymsp[-2].minor.yy192); + yygotominor.yy231->append(yymsp[0].minor.yy192); + } + break; + case 279: /* nexprlist ::= nexprlist COMMA expr */ +{ + yymsp[-2].minor.yy231->append(yymsp[0].minor.yy192); + yygotominor.yy231 = yymsp[-2].minor.yy231; + DONT_INHERIT_TOKENS("nexprlist"); + } + break; + case 280: /* nexprlist ::= exprx */ +{ + yygotominor.yy231 = new ParserExprList(); + yygotominor.yy231->append(yymsp[0].minor.yy192); + } + break; + case 281: /* cmd ::= CREATE uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf */ +{ + yygotominor.yy203 = new SqliteCreateIndex( + *(yymsp[-9].minor.yy291), + false, + *(yymsp[-7].minor.yy319), + *(yymsp[-5].minor.yy319), + *(yymsp[-4].minor.yy319), + *(yymsp[-2].minor.yy63), + *(yymsp[0].minor.yy418) + ); + delete yymsp[-9].minor.yy291; + delete yymsp[-7].minor.yy319; + delete yymsp[-5].minor.yy319; + delete yymsp[-4].minor.yy319; + delete yymsp[-2].minor.yy63; + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy203; + } + break; + case 282: /* cmd ::= CREATE uniqueflag INDEX nm dbnm ON ID_TAB */ +{ yy_destructor(yypParser,156,&yymsp[-3].minor); +} + break; + case 287: /* idxlist_opt ::= */ +{yygotominor.yy63 = new ParserIndexedColumnList();} + break; + case 288: /* idxlist_opt ::= LP idxlist RP */ +{yygotominor.yy63 = yymsp[-1].minor.yy63;} + break; + case 289: /* idxlist ::= idxlist COMMA idxlist_single */ +{ + yymsp[-2].minor.yy63->append(yymsp[0].minor.yy428); + yygotominor.yy63 = yymsp[-2].minor.yy63; + DONT_INHERIT_TOKENS("idxlist"); + } + break; + case 290: /* idxlist ::= idxlist_single */ +{ + yygotominor.yy63 = new ParserIndexedColumnList(); + yygotominor.yy63->append(yymsp[0].minor.yy428); + } + break; + case 291: /* idxlist_single ::= nm sortorder */ + case 292: /* idxlist_single ::= ID_COL */ yytestcase(yyruleno==292); +{ + SqliteIndexedColumn* obj = + new SqliteIndexedColumn( + *(yymsp[-1].minor.yy319), + QString::null, + *(yymsp[0].minor.yy389) + ); + yygotominor.yy428 = obj; + delete yymsp[0].minor.yy389; + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy428; + } + break; + case 293: /* cmd ::= DROP INDEX fullname */ +{ + yygotominor.yy203 = new SqliteDropIndex(false, yymsp[0].minor.yy120->name1, yymsp[0].minor.yy120->name2); + delete yymsp[0].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 296: /* cmd ::= COPY orconf nm dbnm FROM nm USING DELIMITERS STRING */ +{ + yygotominor.yy203 = new SqliteCopy( + *(yymsp[-7].minor.yy418), + *(yymsp[-6].minor.yy319), + *(yymsp[-5].minor.yy319), + *(yymsp[-3].minor.yy319), + yymsp[0].minor.yy0->value + ); + delete yymsp[-7].minor.yy418; + delete yymsp[-6].minor.yy319; + delete yymsp[-5].minor.yy319; + delete yymsp[-3].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 297: /* cmd ::= COPY orconf nm dbnm FROM nm */ +{ + yygotominor.yy203 = new SqliteCopy( + *(yymsp[-4].minor.yy418), + *(yymsp[-3].minor.yy319), + *(yymsp[-2].minor.yy319), + *(yymsp[0].minor.yy319) + ); + delete yymsp[-4].minor.yy418; + delete yymsp[-3].minor.yy319; + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 298: /* cmd ::= VACUUM */ +{ + yygotominor.yy203 = new SqliteVacuum(); + objectForTokens = yygotominor.yy203; + } + break; + case 299: /* cmd ::= VACUUM nm */ +{ + yygotominor.yy203 = new SqliteVacuum(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 300: /* cmd ::= PRAGMA ids */ +{ + yygotominor.yy203 = new SqlitePragma(*(yymsp[0].minor.yy319), QString::null); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 301: /* cmd ::= PRAGMA nm EQ nmnum */ + case 303: /* cmd ::= PRAGMA nm EQ minus_num */ yytestcase(yyruleno==303); +{ + yygotominor.yy203 = new SqlitePragma(*(yymsp[-2].minor.yy319), QString::null, *(yymsp[0].minor.yy69), true); + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy69; + objectForTokens = yygotominor.yy203; + } + break; + case 302: /* cmd ::= PRAGMA nm LP nmnum RP */ + case 304: /* cmd ::= PRAGMA nm LP minus_num RP */ yytestcase(yyruleno==304); +{ + yygotominor.yy203 = new SqlitePragma(*(yymsp[-3].minor.yy319), QString::null, *(yymsp[-1].minor.yy69), false); + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy69; + objectForTokens = yygotominor.yy203; + } + break; + case 308: /* nmnum ::= nm */ +{ + yygotominor.yy69 = new QVariant(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + } + break; + case 309: /* nmnum ::= ON */ + case 310: /* nmnum ::= DELETE */ yytestcase(yyruleno==310); + case 311: /* nmnum ::= DEFAULT */ yytestcase(yyruleno==311); +{yygotominor.yy69 = new QVariant(yymsp[0].minor.yy0->value);} + break; + case 314: /* minus_num ::= MINUS number */ +{ + if (yymsp[0].minor.yy69->type() == QVariant::Double) + *(yymsp[0].minor.yy69) = -(yymsp[0].minor.yy69->toDouble()); + else if (yymsp[0].minor.yy69->type() == QVariant::LongLong) + *(yymsp[0].minor.yy69) = -(yymsp[0].minor.yy69->toLongLong()); + else + Q_ASSERT_X(true, "producing minus number", "QVariant is neither of Double or LongLong."); + + yygotominor.yy69 = yymsp[0].minor.yy69; + } + break; + case 315: /* number ::= INTEGER */ +{yygotominor.yy69 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toLongLong());} + break; + case 316: /* number ::= FLOAT */ +{yygotominor.yy69 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toDouble());} + break; + case 317: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause BEGIN trigger_cmd_list END */ +{ + yygotominor.yy203 = new SqliteCreateTrigger( + *(yymsp[-12].minor.yy226), + false, + *(yymsp[-10].minor.yy319), + *(yymsp[-6].minor.yy319), + *(yymsp[-5].minor.yy319), + *(yymsp[-9].minor.yy372), + yymsp[-8].minor.yy151, + *(yymsp[-4].minor.yy83), + yymsp[-3].minor.yy192, + *(yymsp[-1].minor.yy270), + 2 + ); + delete yymsp[-12].minor.yy226; + delete yymsp[-9].minor.yy372; + delete yymsp[-4].minor.yy83; + delete yymsp[-6].minor.yy319; + delete yymsp[-10].minor.yy319; + delete yymsp[-5].minor.yy319; + delete yymsp[-1].minor.yy270; + objectForTokens = yygotominor.yy203; + } + break; + case 318: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause */ +{ + QList CL; + + yygotominor.yy203 = new SqliteCreateTrigger( + *(yymsp[-9].minor.yy226), + false, + *(yymsp[-7].minor.yy319), + *(yymsp[-3].minor.yy319), + *(yymsp[-2].minor.yy319), + *(yymsp[-6].minor.yy372), + yymsp[-5].minor.yy151, + *(yymsp[-1].minor.yy83), + yymsp[0].minor.yy192, + CL, + 2 + ); + delete yymsp[-9].minor.yy226; + delete yymsp[-6].minor.yy372; + delete yymsp[-1].minor.yy83; + delete yymsp[-3].minor.yy319; + delete yymsp[-7].minor.yy319; + delete yymsp[-2].minor.yy319; + objectForTokens = yygotominor.yy203; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 319: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause BEGIN trigger_cmd_list */ +{ + yygotominor.yy203 = new SqliteCreateTrigger( + *(yymsp[-11].minor.yy226), + false, + *(yymsp[-9].minor.yy319), + *(yymsp[-5].minor.yy319), + *(yymsp[-4].minor.yy319), + *(yymsp[-8].minor.yy372), + yymsp[-7].minor.yy151, + *(yymsp[-3].minor.yy83), + yymsp[-2].minor.yy192, + *(yymsp[0].minor.yy270), + 2 + ); + delete yymsp[-11].minor.yy226; + delete yymsp[-8].minor.yy372; + delete yymsp[-3].minor.yy83; + delete yymsp[-5].minor.yy319; + delete yymsp[-9].minor.yy319; + delete yymsp[-4].minor.yy319; + delete yymsp[0].minor.yy270; + objectForTokens = yygotominor.yy203; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 320: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON ID_TAB|ID_DB */ +{ yy_destructor(yypParser,157,&yymsp[-6].minor); + yy_destructor(yypParser,156,&yymsp[-4].minor); + yy_destructor(yypParser,232,&yymsp[-3].minor); + yy_destructor(yypParser,233,&yymsp[-2].minor); +} + break; + case 321: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm DOT ID_TAB */ +{ yy_destructor(yypParser,157,&yymsp[-8].minor); + yy_destructor(yypParser,156,&yymsp[-6].minor); + yy_destructor(yypParser,232,&yymsp[-5].minor); + yy_destructor(yypParser,233,&yymsp[-4].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 323: /* trigger_time ::= BEFORE */ +{yygotominor.yy372 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::BEFORE);} + break; + case 324: /* trigger_time ::= AFTER */ +{yygotominor.yy372 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::AFTER);} + break; + case 325: /* trigger_time ::= INSTEAD OF */ +{yygotominor.yy372 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::INSTEAD_OF);} + break; + case 326: /* trigger_time ::= */ +{yygotominor.yy372 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::null);} + break; + case 327: /* trigger_event ::= DELETE */ +{ + yygotominor.yy151 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::DELETE); + objectForTokens = yygotominor.yy151; + } + break; + case 328: /* trigger_event ::= INSERT */ +{ + yygotominor.yy151 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::INSERT); + objectForTokens = yygotominor.yy151; + } + break; + case 329: /* trigger_event ::= UPDATE */ +{ + yygotominor.yy151 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::UPDATE); + objectForTokens = yygotominor.yy151; + } + break; + case 330: /* trigger_event ::= UPDATE OF inscollist */ +{ + yygotominor.yy151 = new SqliteCreateTrigger::Event(*(yymsp[0].minor.yy207)); + delete yymsp[0].minor.yy207; + objectForTokens = yygotominor.yy151; + } + break; + case 331: /* foreach_clause ::= */ +{yygotominor.yy83 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::null);} + break; + case 332: /* foreach_clause ::= FOR EACH ROW */ +{yygotominor.yy83 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_ROW);} + break; + case 333: /* foreach_clause ::= FOR EACH STATEMENT */ +{yygotominor.yy83 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT);} + break; + case 336: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ +{ + yymsp[-2].minor.yy270->append(yymsp[-1].minor.yy203); + yygotominor.yy270 = yymsp[-2].minor.yy270; + DONT_INHERIT_TOKENS("trigger_cmd_list"); + } + break; + case 337: /* trigger_cmd_list ::= trigger_cmd SEMI */ +{ + yygotominor.yy270 = new ParserQueryList(); + yygotominor.yy270->append(yymsp[-1].minor.yy203); + } + break; + case 342: /* raisetype ::= ROLLBACK|ABORT|FAIL */ +{yygotominor.yy0 = yymsp[0].minor.yy0;} + break; + case 343: /* cmd ::= DROP TRIGGER fullname */ +{ + yygotominor.yy203 = new SqliteDropTrigger(false, yymsp[0].minor.yy120->name1, yymsp[0].minor.yy120->name2); + delete yymsp[0].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 346: /* cmd ::= ATTACH database_kw_opt ids AS ids key_opt */ +{ + SqliteExpr* e1 = new SqliteExpr(); + SqliteExpr* e2 = new SqliteExpr(); + e1->initLiteral(*(yymsp[-3].minor.yy319)); + e2->initLiteral(*(yymsp[-1].minor.yy319)); + yygotominor.yy203 = new SqliteAttach(*(yymsp[-4].minor.yy291), e1, e2, yymsp[0].minor.yy192); + delete yymsp[-4].minor.yy291; + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 348: /* key_opt ::= USING ids */ +{ + SqliteExpr* e = new SqliteExpr(); + e->initLiteral(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + yygotominor.yy192 = e; + } + break; + case 351: /* cmd ::= DETACH database_kw_opt nm */ +{ + SqliteExpr* e = new SqliteExpr(); + e->initLiteral(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + yygotominor.yy203 = new SqliteDetach(*(yymsp[-1].minor.yy291), e); + delete yymsp[-1].minor.yy291; + objectForTokens = yygotominor.yy203; + } + break; + default: + /* (0) input ::= cmdlist */ yytestcase(yyruleno==0); + break; + }; + } + assert( yyruleno>=0 && yyruleno<(int)(sizeof(yyRuleInfo)/sizeof(yyRuleInfo[0])) ); + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + + // Store tokens for the rule in parser context + QList allTokens; + QList allTokensWithAllInherited; + QString keyForTokensMap; + int tokensMapKeyCnt; + if (parserContext->setupTokens) + { + if (objectForTokens) + { + // In case this is a list with recurrent references we need + // to clear tokens before adding the new and extended list. + objectForTokens->tokens.clear(); + } + + QList tokens; + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + tokens.clear(); + const char* fieldName = yyTokenName[yypParser->yystack[i].major]; + if (parserContext->isManagedToken(yypParser->yystack[i].minor.yy0)) + tokens += yypParser->yystack[i].minor.yy0; + + tokens += *(yypParser->yystack[i].tokens); + + if (!noTokenInheritanceFields.contains(fieldName)) + { + if (objectForTokens) + { + keyForTokensMap = fieldName; + tokensMapKeyCnt = 2; + while (objectForTokens->tokensMap.contains(keyForTokensMap)) + keyForTokensMap = fieldName + QString::number(tokensMapKeyCnt++); + + objectForTokens->tokensMap[keyForTokensMap] = parserContext->getTokenPtrList(tokens); + } + + allTokens += tokens; + } + else + { + // If field is mentioned only once, then only one occurance of it will be ignored. + // Second one should be inherited. See "anylist" definition for explanation why. + noTokenInheritanceFields.removeOne(fieldName); + } + allTokensWithAllInherited += tokens; + } + if (objectForTokens) + { + objectForTokens->tokens += parserContext->getTokenPtrList(allTokens); + } + } + + // Clear token lists + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + delete yypParser->yystack[i].tokens; + yypParser->yystack[i].tokens = nullptr; + } + + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); + if( yyact < YYNSTATE ){ +#ifdef NDEBUG + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if( yysize ){ + yypParser->yyidx++; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yymsp->minor = yygotominor; + if (parserContext->setupTokens) + *(yypParser->yystack[yypParser->yyidx].tokens) = allTokens; + }else +#endif + { + yy_shift(yypParser,yyact,yygoto,&yygotominor); + if (parserContext->setupTokens) + { + QList* tokensPtr = yypParser->yystack[yypParser->yyidx].tokens; + *tokensPtr = allTokensWithAllInherited + *tokensPtr; + } + } + }else{ + assert( yyact == YYNSTATE + YYNRULE + 1 ); + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + sqlite2_parseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ + sqlite2_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + sqlite2_parseARG_FETCH; +#define TOKEN (yyminor.yy0) + + UNUSED_PARAMETER(yymajor); + parserContext->error(TOKEN, QObject::tr("Syntax error")); + sqlite2_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + sqlite2_parseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ + sqlite2_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "sqlite2_parseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
    +**
  • A pointer to the parser (an opaque structure.) +**
  • The major token number. +**
  • The minor token number. +**
  • An option argument of a grammar-specified type. +**
+** +** Outputs: +** None. +*/ +void sqlite2_parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + sqlite2_parseTOKENTYPE yyminor /* The value for the token */ + sqlite2_parseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ +#if YYSTACKDEPTH<=0 + if( yypParser->yystksz <=0 ){ + /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ + yyminorunion = yyzerominor; + yyStackOverflow(yypParser, &yyminorunion); + return; + } +#endif + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + yypParser->yystack[0].tokens = new QList(); + } + yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + sqlite2_parseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s [%s] (lemon type: %s)\n", + yyTracePrompt, + yyminor->value.toLatin1().data(), + yyminor->typeString().toLatin1().data(), + yyTokenName[yymajor]); } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyactyyerrcnt--; + yymajor = YYNOCODE; + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else{ + assert( yyact == YY_ERROR_ACTION ); +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_reduce_action( + yypParser->yystack[yypParser->yyidx].stateno, + YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yyerrorhit = 1; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.h b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.h new file mode 100644 index 0000000..058d397 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.h @@ -0,0 +1,146 @@ +#define TK2_ILLEGAL 1 +#define TK2_COMMENT 2 +#define TK2_SPACE 3 +#define TK2_ID 4 +#define TK2_ABORT 5 +#define TK2_AFTER 6 +#define TK2_ASC 7 +#define TK2_ATTACH 8 +#define TK2_BEFORE 9 +#define TK2_BEGIN 10 +#define TK2_CASCADE 11 +#define TK2_CLUSTER 12 +#define TK2_CONFLICT 13 +#define TK2_COPY 14 +#define TK2_DATABASE 15 +#define TK2_DEFERRED 16 +#define TK2_DELIMITERS 17 +#define TK2_DESC 18 +#define TK2_DETACH 19 +#define TK2_EACH 20 +#define TK2_END 21 +#define TK2_EXPLAIN 22 +#define TK2_FAIL 23 +#define TK2_FOR 24 +#define TK2_GLOB 25 +#define TK2_IGNORE 26 +#define TK2_IMMEDIATE 27 +#define TK2_INITIALLY 28 +#define TK2_INSTEAD 29 +#define TK2_LIKE 30 +#define TK2_MATCH 31 +#define TK2_KEY 32 +#define TK2_OF 33 +#define TK2_OFFSET 34 +#define TK2_PRAGMA 35 +#define TK2_RAISE 36 +#define TK2_REPLACE 37 +#define TK2_RESTRICT 38 +#define TK2_ROW 39 +#define TK2_STATEMENT 40 +#define TK2_TEMP 41 +#define TK2_TRIGGER 42 +#define TK2_VACUUM 43 +#define TK2_VIEW 44 +#define TK2_OR 45 +#define TK2_AND 46 +#define TK2_NOT 47 +#define TK2_EQ 48 +#define TK2_NE 49 +#define TK2_ISNULL 50 +#define TK2_NOTNULL 51 +#define TK2_IS 52 +#define TK2_BETWEEN 53 +#define TK2_IN 54 +#define TK2_GT 55 +#define TK2_GE 56 +#define TK2_LT 57 +#define TK2_LE 58 +#define TK2_BITAND 59 +#define TK2_BITOR 60 +#define TK2_LSHIFT 61 +#define TK2_RSHIFT 62 +#define TK2_PLUS 63 +#define TK2_MINUS 64 +#define TK2_STAR 65 +#define TK2_SLASH 66 +#define TK2_REM 67 +#define TK2_CONCAT 68 +#define TK2_UMINUS 69 +#define TK2_UPLUS 70 +#define TK2_BITNOT 71 +#define TK2_SEMI 72 +#define TK2_TRANSACTION 73 +#define TK2_ID_TRANS 74 +#define TK2_COMMIT 75 +#define TK2_ROLLBACK 76 +#define TK2_CREATE 77 +#define TK2_TABLE 78 +#define TK2_LP 79 +#define TK2_RP 80 +#define TK2_AS 81 +#define TK2_DOT 82 +#define TK2_ID_TAB_NEW 83 +#define TK2_ID_DB 84 +#define TK2_COMMA 85 +#define TK2_ID_COL_NEW 86 +#define TK2_STRING 87 +#define TK2_JOIN_KW 88 +#define TK2_ID_COL_TYPE 89 +#define TK2_DEFAULT 90 +#define TK2_INTEGER 91 +#define TK2_FLOAT 92 +#define TK2_NULL 93 +#define TK2_CONSTRAINT 94 +#define TK2_PRIMARY 95 +#define TK2_UNIQUE 96 +#define TK2_CHECK 97 +#define TK2_REFERENCES 98 +#define TK2_COLLATE 99 +#define TK2_ON 100 +#define TK2_INSERT 101 +#define TK2_DELETE 102 +#define TK2_UPDATE 103 +#define TK2_ID_FK_MATCH 104 +#define TK2_SET 105 +#define TK2_DEFERRABLE 106 +#define TK2_FOREIGN 107 +#define TK2_ID_CONSTR 108 +#define TK2_ID_TAB 109 +#define TK2_DROP 110 +#define TK2_ID_VIEW_NEW 111 +#define TK2_ID_VIEW 112 +#define TK2_UNION 113 +#define TK2_ALL 114 +#define TK2_EXCEPT 115 +#define TK2_INTERSECT 116 +#define TK2_SELECT 117 +#define TK2_DISTINCT 118 +#define TK2_ID_ALIAS 119 +#define TK2_FROM 120 +#define TK2_USING 121 +#define TK2_JOIN 122 +#define TK2_ID_JOIN_OPTS 123 +#define TK2_ORDER 124 +#define TK2_BY 125 +#define TK2_GROUP 126 +#define TK2_HAVING 127 +#define TK2_LIMIT 128 +#define TK2_WHERE 129 +#define TK2_ID_COL 130 +#define TK2_INTO 131 +#define TK2_VALUES 132 +#define TK2_VARIABLE 133 +#define TK2_LIKE_KW 134 +#define TK2_CASE 135 +#define TK2_ID_FN 136 +#define TK2_ID_ERR_MSG 137 +#define TK2_WHEN 138 +#define TK2_THEN 139 +#define TK2_ELSE 140 +#define TK2_INDEX 141 +#define TK2_ID_IDX_NEW 142 +#define TK2_ID_IDX 143 +#define TK2_ID_PRAGMA 144 +#define TK2_ID_TRIG_NEW 145 +#define TK2_ID_TRIG 146 diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.y b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.y new file mode 100644 index 0000000..e44e9b8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.y @@ -0,0 +1,2068 @@ +%token_prefix TK2_ +%token_type {Token*} +%default_type {Token*} +%extra_argument {ParserContext* parserContext} +%name sqlite2_parse + +%syntax_error { + UNUSED_PARAMETER(yymajor); + parserContext->error(TOKEN, QObject::tr("Syntax error")); +} + +%stack_overflow { + UNUSED_PARAMETER(yypMinor); + parserContext->error(QObject::tr("Parser stack overflow")); +} + +%include { +#include "token.h" +#include "parsercontext.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser_helper_stubs.h" +#include "common/utils_sql.h" +#include +#include + +#define assert(X) Q_ASSERT(X) +#define UNUSED_PARAMETER(X) (void)(X) +#define DONT_INHERIT_TOKENS(X) noTokenInheritanceFields << X +} + +// These are extra tokens used by the lexer but never seen by the +// parser. We put them in a rule so that the parser generator will +// add them to the parse.h output file. + +%nonassoc ILLEGAL COMMENT SPACE. + +// The following directive causes tokens ABORT, AFTER, ASC, etc. to +// fallback to ID if they will not parse as their original value. +// This obviates the need for the "id" nonterminal. + +%fallback ID + ABORT AFTER ASC ATTACH BEFORE BEGIN CASCADE CLUSTER CONFLICT + COPY DATABASE DEFERRED DELIMITERS DESC DETACH EACH END EXPLAIN FAIL FOR + GLOB IGNORE IMMEDIATE INITIALLY INSTEAD LIKE MATCH KEY + OF OFFSET PRAGMA RAISE REPLACE RESTRICT ROW STATEMENT + TEMP TRIGGER VACUUM VIEW. + +// Define operator precedence early so that this is the first occurance +// of the operator tokens in the grammer. Keeping the operators together +// causes them to be assigned integer values that are close together, +// which keeps parser tables smaller. + +%left OR. +%left AND. +%right NOT. +%left EQ NE ISNULL NOTNULL IS LIKE GLOB BETWEEN IN. +%left GT GE LT LE. +%left BITAND BITOR LSHIFT RSHIFT. +%left PLUS MINUS. +%left STAR SLASH REM. +%left CONCAT. +%right UMINUS UPLUS BITNOT. + +// Input is a single SQL command +%type cmd {SqliteQuery*} +%destructor cmd {delete $$;} + +input ::= cmdlist. + +cmdlist ::= cmdlist ecmd(C). {parserContext->addQuery(C); DONT_INHERIT_TOKENS("cmdlist");} +cmdlist ::= ecmd(C). {parserContext->addQuery(C);} + +%type ecmd {SqliteQuery*} +%destructor ecmd {delete $$;} +ecmd(X) ::= SEMI. {X = new SqliteEmptyQuery();} +ecmd(X) ::= explain(E) cmdx(C) SEMI. { + X = C; + X->explain = E->explain; + X->queryPlan = E->queryPlan; + delete E; + objectForTokens = X; + } + +%type explain {ParserStubExplain*} +%destructor explain {delete $$;} +explain(X) ::= . {X = new ParserStubExplain(false, false);} +explain(X) ::= EXPLAIN. {X = new ParserStubExplain(true, false);} + +%type cmdx {SqliteQuery*} +%destructor cmdx {delete $$;} +cmdx(X) ::= cmd(C). {X = C;} + +///////////////////// Begin and end transactions. //////////////////////////// + +cmd(X) ::= BEGIN trans_opt(TO) onconf(C). { + X = new SqliteBeginTrans( + TO->transactionKw, + TO->name, + *(C) + ); + delete TO; + delete C; + objectForTokens = X; + } + +%type trans_opt {ParserStubTransDetails*} +%destructor trans_opt {delete $$;} +trans_opt(X) ::= . {X = new ParserStubTransDetails();} +trans_opt(X) ::= TRANSACTION. { + X = new ParserStubTransDetails(); + X->transactionKw = true; + } +trans_opt(X) ::= TRANSACTION nm(N). { + X = new ParserStubTransDetails(); + X->transactionKw = true; + X->name = *(N); + delete N; + } +trans_opt ::= TRANSACTION ID_TRANS. {} + +cmd(X) ::= COMMIT trans_opt(T). { + X = new SqliteCommitTrans( + T->transactionKw, + T->name, + false + ); + delete T; + objectForTokens = X; + } +cmd(X) ::= END trans_opt(T). { + X = new SqliteCommitTrans( + T->transactionKw, + T->name, + true + ); + delete T; + objectForTokens = X; + } +cmd(X) ::= ROLLBACK trans_opt(T). { + X = new SqliteRollback( + T->transactionKw, + T->name + ); + delete T; + objectForTokens = X; + } + +///////////////////// The CREATE TABLE statement //////////////////////////// + +cmd(X) ::= CREATE temp(T) TABLE + fullname(N) + LP columnlist(CL) + conslist_opt(CS) RP. { + X = new SqliteCreateTable( + *(T), + false, + N->name1, + N->name2, + *(CL), + *(CS) + ); + delete T; + delete CL; + delete CS; + delete N; + objectForTokens = X; + } +cmd(X) ::= CREATE temp(T) TABLE + fullname(N) + AS select(S). { + X = new SqliteCreateTable( + *(T), + false, + N->name1, + N->name2, + S + ); + delete T; + delete N; + objectForTokens = X; + } +cmd ::= CREATE temp TABLE + nm DOT ID_TAB_NEW. {} +cmd ::= CREATE temp TABLE + ID_DB|ID_TAB_NEW. {} + +%type temp {int*} +%destructor temp {delete $$;} +temp(X) ::= TEMP(T). {X = new int( (T->value.length() > 4) ? 2 : 1 );} +temp(X) ::= . {X = new int(0);} + +%type columnlist {ParserCreateTableColumnList*} +%destructor columnlist {delete $$;} +columnlist(X) ::= columnlist(L) + COMMA column(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("columnlist"); + } +columnlist(X) ::= column(C). { + X = new ParserCreateTableColumnList(); + X->append(C); + } + +%type column {SqliteCreateTable::Column*} +%destructor column {delete $$;} +column(X) ::= columnid(C) type(T) + carglist(L). { + X = new SqliteCreateTable::Column(*(C), T, *(L)); + delete C; + delete L; + objectForTokens = X; + } + +%type columnid {QString*} +%destructor columnid {delete $$;} +columnid(X) ::= nm(N). {X = N;} +columnid ::= ID_COL_NEW. {} + +// An IDENTIFIER can be a generic identifier, or one of several +// keywords. Any non-standard keyword can also be an identifier. + +%type id {QString*} +%destructor id {delete $$;} +id(X) ::= ID(T). { + X = new QString( + stripObjName( + T->value, + parserContext->dialect + ) + ); + } + +// And "ids" is an identifer-or-string. + +%type ids {QString*} +%destructor ids {delete $$;} +ids(X) ::= ID|STRING(T). {X = new QString(T->value);} + +// The name of a column or table can be any of the following: + +%type nm {QString*} +%destructor nm {delete $$;} +nm(X) ::= id(N). {X = N;} +nm(X) ::= STRING(N). {X = new QString(stripString(N->value));} +nm(X) ::= JOIN_KW(N). {X = new QString(N->value);} + +%type type {SqliteColumnType*} +%destructor type {delete $$;} +type(X) ::= . {X = nullptr;} +type(X) ::= typetoken(T). {X = T;} + +%type typetoken {SqliteColumnType*} +%destructor typetoken {delete $$;} +typetoken(X) ::= typename(N). { + X = new SqliteColumnType(*(N)); + delete N; + objectForTokens = X; + } +typetoken(X) ::= typename(N) + LP signed(P) RP. { + X = new SqliteColumnType(*(N), *(P)); + delete N; + delete P; + objectForTokens = X; + } +typetoken(X) ::= typename(N) LP signed(P) + COMMA signed(S) RP. { + X = new SqliteColumnType(*(N), *(P), *(S)); + delete N; + delete P; + delete S; + objectForTokens = X; + } + +%type typename {QString*} +%destructor typename {delete $$;} +typename(X) ::= ids(I). {X = I;} +typename(X) ::= typename(T) ids(I). { + T->append(" " + *(I)); + delete I; + X = T; + } +typename ::= ID_COL_TYPE. {} + +%type signed {QVariant*} +%destructor signed {delete $$;} +signed(X) ::= plus_num(N). {X = N;} +signed(X) ::= minus_num(N). {X = N;} + +%type carglist {ParserCreateTableColumnConstraintList*} +%destructor carglist {delete $$;} +carglist(X) ::= carglist(L) ccons(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("carglist"); + } +carglist(X) ::= carglist(L) ccons_nm(N) + ccons(C). { + L->append(N); + L->append(C); + X = L; + DONT_INHERIT_TOKENS("carglist"); + } +carglist(X) ::= carglist(L) carg(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("carglist"); + } +carglist(X) ::= . {X = new ParserCreateTableColumnConstraintList();} + +%type carg {SqliteCreateTable::Column::Constraint*} +%destructor carg {delete $$;} +carg(X) ::= DEFAULT STRING(S). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefId(stripObjName( + S->value, + parserContext->dialect + )); + objectForTokens = X; + } +carg(X) ::= DEFAULT ID(I). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefId(stripObjName( + I->value, + parserContext->dialect + )); + objectForTokens = X; + + } +carg(X) ::= DEFAULT INTEGER(I). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(I->value).toLongLong(); + X->initDefTerm(val, false); + objectForTokens = X; + } +carg(X) ::= DEFAULT PLUS INTEGER(I). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(I->value).toLongLong(); + X->initDefTerm(val, false); + objectForTokens = X; + } +carg(X) ::= DEFAULT MINUS INTEGER(I). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(I->value).toLongLong(); + X->initDefTerm(val, true); + objectForTokens = X; + } +carg(X) ::= DEFAULT FLOAT(F). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(F->value).toDouble(); + X->initDefTerm(val, false); + objectForTokens = X; + } +carg(X) ::= DEFAULT PLUS FLOAT(F). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(F->value).toDouble(); + X->initDefTerm(val, false); + objectForTokens = X; + } +carg(X) ::= DEFAULT MINUS FLOAT(F). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(F->value).toDouble(); + X->initDefTerm(val, true); + objectForTokens = X; + } +carg(X) ::= DEFAULT NULL. { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefTerm(QVariant(), false); + objectForTokens = X; + } + +// In addition to the type name, we also care about the primary key and +// UNIQUE constraints. + +%type ccons_nm {SqliteCreateTable::Column::Constraint*} +%destructor ccons_nm {delete $$;} +ccons_nm(X) ::= CONSTRAINT nm(N). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefNameOnly(*(N)); + delete N; + objectForTokens = X; + } + +%type ccons {SqliteCreateTable::Column::Constraint*} +%destructor ccons {delete $$;} +ccons(X) ::= NULL onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initNull(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= NOT NULL onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initNotNull(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= PRIMARY KEY sortorder(O) + onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initPk(*(O), *(C), false); + delete O; + delete C; + objectForTokens = X; + } +ccons(X) ::= UNIQUE onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initUnique(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= CHECK LP expr(E) RP onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initCheck(E, *(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= REFERENCES nm(N) + idxlist_opt(I) refargs(A). { + X = new SqliteCreateTable::Column::Constraint(); + X->initFk(*(N), *(I), *(A)); + delete N; + delete A; + delete I; + objectForTokens = X; + } +ccons(X) ::= defer_subclause(D). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefer(D->initially, D->deferrable); + delete D; + objectForTokens = X; + } +ccons(X) ::= COLLATE id(I). { + X = new SqliteCreateTable::Column::Constraint(); + X->initColl(*(I)); + delete I; + objectForTokens = X; + } +ccons(X) ::= CHECK LP RP. { + X = new SqliteCreateTable::Column::Constraint(); + X->initCheck(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +// The next group of rules parses the arguments to a REFERENCES clause +// that determine if the referential integrity checking is deferred or +// or immediate and which determine what action to take if a ref-integ +// check fails. + +%type refargs {ParserFkConditionList*} +%destructor refargs {delete $$;} +refargs(X) ::= . {X = new ParserFkConditionList();} +refargs(X) ::= refargs(L) refarg(A). { + L->append(A); + X = L; + DONT_INHERIT_TOKENS("refargs"); + } + +%type refarg {SqliteForeignKey::Condition*} +%destructor refarg {delete $$;} +refarg(X) ::= MATCH nm(N). { + X = new SqliteForeignKey::Condition(*(N)); + delete N; + } +refarg(X) ::= ON INSERT refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::INSERT, *(R)); delete R;} +refarg(X) ::= ON DELETE refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::DELETE, *(R)); delete R;} +refarg(X) ::= ON UPDATE refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::UPDATE, *(R)); delete R;} +refarg ::= MATCH ID_FK_MATCH. {} + +%type refact {SqliteForeignKey::Condition::Reaction*} +%destructor refact {delete $$;} +refact(X) ::= SET NULL. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_NULL);} +refact(X) ::= SET DEFAULT. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_DEFAULT);} +refact(X) ::= CASCADE. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::CASCADE);} +refact(X) ::= RESTRICT. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::RESTRICT);} + +%type defer_subclause {ParserDeferSubClause*} +%destructor defer_subclause {delete $$;} +defer_subclause(X) ::= NOT DEFERRABLE + init_deferred_pred_opt(I). { + X = new ParserDeferSubClause(SqliteDeferrable::NOT_DEFERRABLE, *(I)); + delete I; + } +defer_subclause(X) ::= DEFERRABLE + init_deferred_pred_opt(I). { + X = new ParserDeferSubClause(SqliteDeferrable::DEFERRABLE, *(I)); + delete I; + } + +%type init_deferred_pred_opt {SqliteInitially*} +%destructor init_deferred_pred_opt {delete $$;} +init_deferred_pred_opt(X) ::= . {X = new SqliteInitially(SqliteInitially::null);} +init_deferred_pred_opt(X) ::= INITIALLY + DEFERRED. {X = new SqliteInitially(SqliteInitially::DEFERRED);} +init_deferred_pred_opt(X) ::= INITIALLY + IMMEDIATE. {X = new SqliteInitially(SqliteInitially::IMMEDIATE);} + + +%type conslist_opt {ParserCreateTableConstraintList*} +%destructor conslist_opt {delete $$;} +conslist_opt(X) ::= . {X = new ParserCreateTableConstraintList();} +conslist_opt(X) ::= COMMA conslist(L). {X = L;} + +%type conslist {ParserCreateTableConstraintList*} +%destructor conslist {delete $$;} +conslist(X) ::= conslist(L) tconscomma(CM) + tcons(C). { + C->afterComma = *(CM); + L->append(C); + X = L; + delete CM; + DONT_INHERIT_TOKENS("conslist"); + } +conslist(X) ::= tcons(C). { + X = new ParserCreateTableConstraintList(); + X->append(C); + } + +%type tconscomma {bool*} +%destructor tconscomma {delete $$;} +tconscomma(X) ::= COMMA. {X = new bool(true);} +tconscomma(X) ::= . {X = new bool(false);} + +%type tcons {SqliteCreateTable::Constraint*} +%destructor tcons {delete $$;} +tcons(X) ::= CONSTRAINT nm(N). { + X = new SqliteCreateTable::Constraint(); + X->initNameOnly(*(N)); + delete N; + objectForTokens = X; + } +tcons(X) ::= PRIMARY KEY LP idxlist(L) + RP onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initPk(*(L), false, *(C)); + delete C; + delete L; + objectForTokens = X; + } +tcons(X) ::= UNIQUE LP idxlist(L) RP + onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initUnique(*(L), *(C)); + delete C; + delete L; + objectForTokens = X; + } +tcons(X) ::= CHECK LP expr(E) RP onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initCheck(E, *(C)); + objectForTokens = X; + } +tcons(X) ::= FOREIGN KEY LP idxlist(L) RP + REFERENCES nm(N) idxlist_opt(IL) + refargs(R) defer_subclause_opt(D). { + X = new SqliteCreateTable::Constraint(); + X->initFk( + *(L), + *(N), + *(IL), + *(R), + D->initially, + D->deferrable + ); + delete N; + delete R; + delete D; + delete IL; + delete L; + objectForTokens = X; + } + +tcons ::= CONSTRAINT ID_CONSTR. {} +tcons ::= FOREIGN KEY LP idxlist RP + REFERENCES ID_TAB. {} +tcons(X) ::= CHECK LP RP onconf. { + X = new SqliteCreateTable::Constraint(); + X->initCheck(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type defer_subclause_opt {ParserDeferSubClause*} +%destructor defer_subclause_opt {delete $$;} +defer_subclause_opt(X) ::= . {X = new ParserDeferSubClause(SqliteDeferrable::null, SqliteInitially::null);} +defer_subclause_opt(X) ::= + defer_subclause(D). {X = D;} + +// The following is a non-standard extension that allows us to declare the +// default behavior when there is a constraint conflict. +%type onconf {SqliteConflictAlgo*} +%destructor onconf {delete $$;} +onconf(X) ::= . {X = new SqliteConflictAlgo(SqliteConflictAlgo::null);} +onconf(X) ::= ON CONFLICT resolvetype(R). {X = R;} + +%type orconf {SqliteConflictAlgo*} +%destructor orconf {delete $$;} +orconf(X) ::= . {X = new SqliteConflictAlgo(SqliteConflictAlgo::null);} +orconf(X) ::= OR resolvetype(R). {X = R;} + +%type resolvetype {SqliteConflictAlgo*} +%destructor resolvetype {delete $$;} +resolvetype(X) ::= ROLLBACK(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= ABORT(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= FAIL(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= IGNORE(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= REPLACE(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} + +////////////////////////// The DROP TABLE ///////////////////////////////////// + +cmd(X) ::= DROP TABLE fullname(N). { + X = new SqliteDropTable(false, N->name1, N->name2); + delete N; + objectForTokens = X; + } + +cmd ::= DROP TABLE nm DOT + ID_TAB. {} +cmd ::= DROP TABLE ID_DB|ID_TAB. {} + +///////////////////// The CREATE VIEW statement ///////////////////////////// + +cmd(X) ::= CREATE temp(T) VIEW + nm(N) + AS select(S). { + X = new SqliteCreateView(*(T), false, *(N), QString::null, S); + delete T; + delete N; + objectForTokens = X; + } + +cmd ::= CREATE temp VIEW ID_VIEW_NEW. {} + +cmd(X) ::= DROP VIEW nm(N). { + X = new SqliteDropView(false, *(N), QString::null); + delete N; + objectForTokens = X; + } + +cmd ::= DROP VIEW ID_VIEW. {} + +//////////////////////// The SELECT statement ///////////////////////////////// + +cmd(X) ::= select_stmt(S). { + X = S; + objectForTokens = X; + } + +%type select_stmt {SqliteQuery*} +%destructor select_stmt {delete $$;} +select_stmt(X) ::= select(S). { + X = S; + // since it's used in trigger: + objectForTokens = X; + } + +%type select {SqliteSelect*} +%destructor select {delete $$;} +select(X) ::= oneselect(S). { + X = SqliteSelect::append(S); + objectForTokens = X; + } +select(X) ::= select(S1) multiselect_op(O) + oneselect(S2). { + X = SqliteSelect::append(S1, *(O), S2); + delete O; + objectForTokens = X; + } + +%type multiselect_op {SqliteSelect::CompoundOperator*} +%destructor multiselect_op {delete $$;} +multiselect_op(X) ::= UNION. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION);} +multiselect_op(X) ::= UNION ALL. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION_ALL);} +multiselect_op(X) ::= EXCEPT. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::EXCEPT);} +multiselect_op(X) ::= INTERSECT. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::INTERSECT);} + +%type oneselect {SqliteSelect::Core*} +%destructor oneselect {delete $$;} +oneselect(X) ::= SELECT distinct(D) + selcollist(L) from(F) + where_opt(W) groupby_opt(G) + having_opt(H) + orderby_opt(O) + limit_opt(LI). { + X = new SqliteSelect::Core( + *(D), + *(L), + F, + W, + *(G), + H, + *(O), + LI + ); + delete L; + delete D; + delete G; + delete O; + objectForTokens = X; + } + +%type distinct {int*} +%destructor distinct {delete $$;} +distinct(X) ::= DISTINCT. {X = new int(1);} +distinct(X) ::= ALL. {X = new int(2);} +distinct(X) ::= . {X = new int(0);} + +// selcollist is a list of expressions that are to become the return +// values of the SELECT statement. The "*" in statements like +// "SELECT * FROM ..." is encoded as a special expression with an +// opcode of TK_ALL. + +%type sclp {ParserResultColumnList*} +%destructor sclp {delete $$;} +sclp(X) ::= selcollist(L) COMMA. {X = L;} +sclp(X) ::= . {X = new ParserResultColumnList();} + +%type selcollist {ParserResultColumnList*} +%destructor selcollist {delete $$;} +selcollist(X) ::= sclp(L) expr(E) as(N). { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + E, + N ? N->asKw : false, + N ? N->name : QString::null + ); + + L->append(obj); + X = L; + delete N; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L) STAR. { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn(true); + + L->append(obj); + X = L; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L) nm(N) DOT STAR. { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + true, + *(N) + ); + L->append(obj); + X = L; + delete N; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L). { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = L; + } +selcollist ::= sclp ID_TAB DOT STAR. {} + +// An option "AS " phrase that can follow one of the expressions that +// define the result set, or one of the tables in the FROM clause. + +%type as {ParserStubAlias*} +%destructor as {delete $$;} +as(X) ::= AS nm(N). { + X = new ParserStubAlias(*(N), true); + delete N; + } +as(X) ::= ids(N). { + X = new ParserStubAlias(*(N), false); + delete N; + } +as ::= AS ID_ALIAS. {} +as ::= ID_ALIAS. {} +as(X) ::= . {X = nullptr;} + +// A complete FROM clause. +%type from {SqliteSelect::Core::JoinSource*} +%destructor from {delete $$;} +from(X) ::= . {X = nullptr;} +from(X) ::= FROM joinsrc(L). {X = L;} + +%type joinsrc {SqliteSelect::Core::JoinSource*} +%destructor joinsrc {delete $$;} +joinsrc(X) ::= singlesrc(S) seltablist(L). { + X = new SqliteSelect::Core::JoinSource( + S, + *(L) + ); + delete L; + objectForTokens = X; + } +joinsrc(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::JoinSource(); + objectForTokens = X; + } + +%type seltablist {ParserOtherSourceList*} +%destructor seltablist {delete $$;} +seltablist(X) ::= seltablist(L) joinop(O) + singlesrc(S) + joinconstr_opt(C). { + SqliteSelect::Core::JoinSourceOther* src = + new SqliteSelect::Core::JoinSourceOther(O, S, C); + + L->append(src); + X = L; + objectForTokens = src; + DONT_INHERIT_TOKENS("seltablist"); + } +seltablist(X) ::= . { + X = new ParserOtherSourceList(); + } + +%type singlesrc {SqliteSelect::Core::SingleSource*} +%destructor singlesrc {delete $$;} +singlesrc(X) ::= nm(N1) dbnm(N2) as(A). { + X = new SqliteSelect::Core::SingleSource( + *(N1), + *(N2), + A ? A->asKw : false, + A ? A->name : QString::null, + false, + QString::null + ); + delete N1; + delete N2; + delete A; + objectForTokens = X; + } +singlesrc(X) ::= LP select(S) RP as(A). { + X = new SqliteSelect::Core::SingleSource( + S, + A ? A->asKw : false, + A ? A->name : QString::null + ); + delete A; + objectForTokens = X; + } +singlesrc(X) ::= LP joinsrc(J) RP as(A). { + X = new SqliteSelect::Core::SingleSource( + J, + A ? A->asKw : false, + A ? A->name : QString::null + ); + delete A; + objectForTokens = X; + } +singlesrc(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::SingleSource(); + objectForTokens = X; + } +singlesrc(X) ::= nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::SingleSource(); + X->database = *(N); + delete N; + objectForTokens = X; + } + +singlesrc ::= nm DOT ID_TAB. {} +singlesrc ::= ID_DB|ID_TAB. {} +singlesrc ::= nm DOT ID_VIEW. {} +singlesrc ::= ID_DB|ID_VIEW. {} + +%type joinconstr_opt {SqliteSelect::Core::JoinConstraint*} +%destructor joinconstr_opt {delete $$;} +joinconstr_opt(X) ::= ON expr(E). { + X = new SqliteSelect::Core::JoinConstraint(E); + objectForTokens = X; + } +joinconstr_opt(X) ::= USING LP + inscollist(L) RP. { + X = new SqliteSelect::Core::JoinConstraint(*(L)); + delete L; + objectForTokens = X; + } +joinconstr_opt(X) ::= . {X = nullptr;} + +%type dbnm {QString*} +%destructor dbnm {delete $$;} +dbnm(X) ::= . {X = new QString();} +dbnm(X) ::= DOT nm(N). {X = N;} + +%type fullname {ParserFullName*} +%destructor fullname {delete $$;} +fullname(X) ::= nm(N1) dbnm(N2). { + X = new ParserFullName(); + X->name1 = *(N1); + X->name2 = *(N2); + delete N1; + delete N2; + } + +%type joinop {SqliteSelect::Core::JoinOp*} +%destructor joinop {delete $$;} +joinop(X) ::= COMMA. { + X = new SqliteSelect::Core::JoinOp(true); + objectForTokens = X; + } +joinop(X) ::= JOIN. { + X = new SqliteSelect::Core::JoinOp(false); + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value); + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) nm(N) JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value, *(N)); + delete N; + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) nm(N1) nm(N2) + JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value, *(N1), *(N2)); + delete N1; + delete N1; + objectForTokens = X; + } + +joinop ::= ID_JOIN_OPTS. {} + +%type orderby_opt {ParserOrderByList*} +%destructor orderby_opt {delete $$;} +orderby_opt(X) ::= . {X = new ParserOrderByList();} +orderby_opt(X) ::= ORDER BY sortlist(L). {X = L;} + +%type sortlist {ParserOrderByList*} +%destructor sortlist {delete $$;} +sortlist(X) ::= sortlist(L) COMMA + collate(C) + expr(E) sortorder(O). { + SqliteOrderBy* obj; + if (C) + { + SqliteExpr* coll = new SqliteExpr(); + coll->initCollate(E, *(C)); + delete C; + obj = new SqliteOrderBy(coll, *(O)); + } + else + { + obj = new SqliteOrderBy(E, *(O)); + } + L->append(obj); + X = L; + delete O; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sortlist"); + } +sortlist(X) ::= expr(E) collate(C) + sortorder(O). { + SqliteOrderBy* obj; + if (C) + { + SqliteExpr* coll = new SqliteExpr(); + coll->initCollate(E, *(C)); + delete C; + obj = new SqliteOrderBy(coll, *(O)); + } + else + { + obj = new SqliteOrderBy(E, *(O)); + } + X = new ParserOrderByList(); + X->append(obj); + delete O; + objectForTokens = obj; + } + +%type collate {QString*} +%destructor collate {if ($$) delete $$;} +collate(X) ::= . {X = nullptr;} +collate(X) ::= COLLATE id(I). {X = I;} + +%type sortorder {SqliteSortOrder*} +%destructor sortorder {delete $$;} +sortorder(X) ::= ASC. {X = new SqliteSortOrder(SqliteSortOrder::ASC);} +sortorder(X) ::= DESC. {X = new SqliteSortOrder(SqliteSortOrder::DESC);} +sortorder(X) ::= . {X = new SqliteSortOrder(SqliteSortOrder::null);} + +%type groupby_opt {ParserExprList*} +%destructor groupby_opt {delete $$;} +groupby_opt(X) ::= . {X = new ParserExprList();} +groupby_opt(X) ::= GROUP BY nexprlist(L). {X = L;} +groupby_opt(X) ::= GROUP BY. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserExprList(); + } +%type having_opt {SqliteExpr*} +%destructor having_opt {delete $$;} +having_opt(X) ::= . {X = nullptr;} +having_opt(X) ::= HAVING expr(E). {X = E;} + +%type limit_opt {SqliteLimit*} +%destructor limit_opt {delete $$;} +limit_opt(X) ::= . {X = nullptr;} +limit_opt(X) ::= LIMIT signed(V). { + X = new SqliteLimit(*(V)); + delete V; + objectForTokens = X; + } +limit_opt(X) ::= LIMIT signed(V1) OFFSET + signed(V2). { + SqliteExpr* expr1 = new SqliteExpr(); + expr1->initLiteral(*(V1)); + expr1->setParent(X); + + SqliteExpr* expr2 = new SqliteExpr(); + expr1->initLiteral(*(V2)); + expr1->setParent(X); + + X = new SqliteLimit(expr1, expr2, true); + + TokenPtr limitToken = TokenPtr::create(Token::INTEGER, V1->toString()); + parserContext->addManagedToken(limitToken); + expr1->tokens << limitToken; + expr1->tokensMap["term"] << limitToken; + + TokenPtr offsetToken = TokenPtr::create(Token::INTEGER, V2->toString()); + parserContext->addManagedToken(offsetToken); + expr2->tokens << offsetToken; + expr2->tokensMap["term"] << offsetToken; + + delete V1; + delete V2; + objectForTokens = X; + } +limit_opt(X) ::= LIMIT signed(V1) COMMA + signed(V2). { + SqliteExpr* expr1 = new SqliteExpr(); + expr1->initLiteral(*(V1)); + expr1->setParent(X); + + SqliteExpr* expr2 = new SqliteExpr(); + expr1->initLiteral(*(V2)); + expr1->setParent(X); + + X = new SqliteLimit(expr1, expr2, false); + + TokenPtr limitToken = TokenPtr::create(Token::INTEGER, V1->toString()); + parserContext->addManagedToken(limitToken); + expr1->tokens << limitToken; + expr1->tokensMap["term"] << limitToken; + + TokenPtr offsetToken = TokenPtr::create(Token::INTEGER, V2->toString()); + parserContext->addManagedToken(offsetToken); + expr2->tokens << offsetToken; + expr2->tokensMap["term"] << offsetToken; + + delete V1; + delete V2; + objectForTokens = X; + } + +/////////////////////////// The DELETE statement ///////////////////////////// + +cmd(X) ::= delete_stmt(S). { + X = S; + objectForTokens = X; + } + +%type delete_stmt {SqliteQuery*} +%destructor delete_stmt {delete $$;} +delete_stmt(X) ::= DELETE FROM fullname(N) + where_opt(W). { + X = new SqliteDelete( + N->name1, + N->name2, + false, + W, + nullptr + ); + delete N; + // since it's used in trigger: + objectForTokens = X; + } + +delete_stmt(X) ::= DELETE FROM. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + X = q; + objectForTokens = X; + } +delete_stmt(X) ::= DELETE FROM nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->database = *(N); + X = q; + objectForTokens = X; + delete N; + } +delete_stmt ::= DELETE FROM nm DOT ID_TAB. {} +delete_stmt ::= DELETE FROM ID_DB|ID_TAB. {} + +%type where_opt {SqliteExpr*} +%destructor where_opt {delete $$;} +where_opt(X) ::= . {X = nullptr;} +where_opt(X) ::= WHERE expr(E). {X = E;} +where_opt(X) ::= WHERE. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteExpr(); + } + + +////////////////////////// The UPDATE command //////////////////////////////// + +cmd(X) ::= update_stmt(S). { + X = S; + objectForTokens = X; + } + +%type update_stmt {SqliteQuery*} +%destructor update_stmt {delete $$;} +update_stmt(X) ::= UPDATE orconf(C) + fullname(N) SET + setlist(L) where_opt(W). { + X = new SqliteUpdate( + *(C), + N->name1, + N->name2, + false, + QString::null, + *(L), + W, + nullptr + ); + delete C; + delete N; + delete L; + // since it's used in trigger: + objectForTokens = X; + } + +update_stmt(X) ::= UPDATE + orconf(C). { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteUpdate(); + objectForTokens = X; + delete C; + } +update_stmt(X) ::= UPDATE + orconf(C) nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->database = *(N); + X = q; + objectForTokens = X; + delete C; + delete N; + } +update_stmt ::= UPDATE orconf nm DOT + ID_TAB. {} +update_stmt ::= UPDATE orconf ID_DB|ID_TAB. {} + +%type setlist {ParserSetValueList*} +%destructor setlist {delete $$;} +setlist(X) ::= setlist(L) COMMA nm(N) EQ + expr(E). { + L->append(ParserSetValue(*(N), E)); + X = L; + delete N; + DONT_INHERIT_TOKENS("setlist"); + } +setlist(X) ::= nm(N) EQ expr(E). { + X = new ParserSetValueList(); + X->append(ParserSetValue(*(N), E)); + delete N; + } +setlist(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserSetValueList(); + } +setlist(X) ::= setlist(L) COMMA. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = L; + } + +setlist ::= setlist COMMA ID_COL. {} +setlist ::= ID_COL. {} + +////////////////////////// The INSERT command ///////////////////////////////// + +cmd(X) ::= insert_stmt(S). { + X = S; + objectForTokens = X; + } + +%type insert_stmt {SqliteQuery*} +%destructor insert_stmt {delete $$;} +insert_stmt(X) ::= insert_cmd(C) INTO + fullname(N) inscollist_opt(I) + VALUES LP exprlist(L) RP. { + X = new SqliteInsert( + C->replace, + C->orConflict, + N->name1, + N->name2, + *(I), + *(L), + nullptr + ); + delete N; + delete C; + delete L; + delete I; + // since it's used in trigger: + objectForTokens = X; + } +insert_stmt(X) ::= insert_cmd(C) INTO + fullname(N) inscollist_opt(I) + select(S). { + X = new SqliteInsert( + C->replace, + C->orConflict, + N->name1, + N->name2, + *(I), + S, + nullptr + ); + delete N; + delete C; + delete I; + // since it's used in trigger: + objectForTokens = X; + } + +insert_stmt(X) ::= insert_cmd(C) INTO. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = C->replace; + q->onConflict = C->orConflict; + X = q; + objectForTokens = X; + delete C; + } +insert_stmt(X) ::= insert_cmd(C) INTO nm(N) + DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = C->replace; + q->onConflict = C->orConflict; + q->database = *(N); + X = q; + objectForTokens = X; + delete C; + delete N; + } +insert_stmt ::= insert_cmd INTO + ID_DB|ID_TAB. {} +insert_stmt ::= insert_cmd INTO + nm DOT ID_TAB. {} + +%type insert_cmd {ParserStubInsertOrReplace*} +%destructor insert_cmd {delete $$;} +insert_cmd(X) ::= INSERT orconf(C). { + X = new ParserStubInsertOrReplace(false, *(C)); + delete C; + } +insert_cmd(X) ::= REPLACE. {X = new ParserStubInsertOrReplace(true);} + +%type inscollist_opt {ParserStringList*} +%destructor inscollist_opt {delete $$;} +inscollist_opt(X) ::= . {X = new ParserStringList();} +inscollist_opt(X) ::= LP inscollist(L) RP. {X = L;} + +%type inscollist {ParserStringList*} +%destructor inscollist {delete $$;} +inscollist(X) ::= inscollist(L) COMMA + nm(N). { + L->append(*(N)); + X = L; + delete N; + DONT_INHERIT_TOKENS("inscollist"); + } +inscollist(X) ::= nm(N). { + X = new ParserStringList(); + X->append(*(N)); + delete N; + } +inscollist(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserStringList(); + } + +inscollist ::= inscollist COMMA ID_COL. {} +inscollist ::= ID_COL. {} + +/////////////////////////// Expression Processing ///////////////////////////// + +%type exprx {SqliteExpr*} +%destructor exprx {delete $$;} +exprx(X) ::= NULL. { + X = new SqliteExpr(); + X->initNull(); + objectForTokens = X; + } +exprx(X) ::= INTEGER(I). { + X = new SqliteExpr(); + QVariant val = QVariant(I->value).toLongLong(); + X->initLiteral(val); + objectForTokens = X; + } +exprx(X) ::= FLOAT(F). { + X = new SqliteExpr(); + QVariant val = QVariant(F->value).toDouble(); + X->initLiteral(val); + objectForTokens = X; + } +exprx(X) ::= STRING(S). { + X = new SqliteExpr(); + X->initLiteral(QVariant(S->value)); + objectForTokens = X; + } +exprx(X) ::= LP expr(E) RP. { + X = new SqliteExpr(); + X->initSubExpr(E); + objectForTokens = X; + } +exprx(X) ::= id(N). { + X = new SqliteExpr(); + X->initId(*(N)); + delete N; + objectForTokens = X; + } +exprx(X) ::= JOIN_KW(N). { + X = new SqliteExpr(); + X->initId(N->value); + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT nm(N2). { + X = new SqliteExpr(); + X->initId(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT nm(N2) DOT nm(N3). { + X = new SqliteExpr(); + X->initId(*(N1), *(N2), *(N3)); + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } +exprx(X) ::= VARIABLE(V). { + X = new SqliteExpr(); + X->initBindParam(V->value); + objectForTokens = X; + } +exprx(X) ::= ID(I) LP exprlist(L) RP. { + X = new SqliteExpr(); + X->initFunction(I->value, false, *(L)); + delete L; + objectForTokens = X; + } +exprx(X) ::= ID(I) LP STAR RP. { + X = new SqliteExpr(); + X->initFunction(I->value, true); + objectForTokens = X; + } +exprx(X) ::= expr(E1) AND(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) OR(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) LT|GT|GE|LE(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) EQ|NE(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) + BITAND|BITOR|LSHIFT|RSHIFT(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) PLUS|MINUS(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) STAR|SLASH|REM(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) CONCAT(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) likeop(L) + expr(E2). [LIKE_KW] { + X = new SqliteExpr(); + X->initLike(E1, *(N), *(L), E2); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= expr(E) ISNULL|NOTNULL(N). { + X = new SqliteExpr(); + X->initNull(E, N->value); + objectForTokens = X; + } +exprx(X) ::= expr(E) NOT NULL. { + X = new SqliteExpr(); + X->initNull(E, "NOT NULL"); + objectForTokens = X; + } +exprx(X) ::= expr(E1) IS not_opt(N) + expr(E2). { + X = new SqliteExpr(); + X->initIs(E1, *(N), E2); + delete N; + objectForTokens = X; + } +exprx(X) ::= NOT(O) expr(E). { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + } +exprx(X) ::= BITNOT(O) expr(E). { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= MINUS(O) expr(E). [BITNOT] { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= PLUS(O) expr(E). [BITNOT] { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) BETWEEN + expr(E2) AND + expr(E3). [BETWEEN] { + X = new SqliteExpr(); + X->initBetween(E1, *(N), E2, E3); + delete N; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN LP + exprlist(L) RP. [IN] { + X = new SqliteExpr(); + X->initIn(E, *(N), *(L)); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN LP + select(S) RP. [IN] { + X = new SqliteExpr(); + X->initIn(E, *(N), S); + delete N; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN nm(N1) + dbnm(N2). [IN] { + X = new SqliteExpr(); + X->initIn(E, N, *(N1), *(N2)); + delete N; + delete N1; + objectForTokens = X; + } +exprx(X) ::= LP select(S) RP. { + X = new SqliteExpr(); + X->initSubSelect(S); + objectForTokens = X; + } +exprx(X) ::= CASE case_operand(O) + case_exprlist(L) + case_else(E) END. { + X = new SqliteExpr(); + X->initCase(O, *(L), E); + delete L; + objectForTokens = X; + } +exprx(X) ::= RAISE LP raisetype(R) COMMA + nm(N) RP. { + X = new SqliteExpr(); + X->initRaise(R->value, *(N)); + delete N; + objectForTokens = X; + } +exprx(X) ::= RAISE LP IGNORE(R) RP. { + X = new SqliteExpr(); + X->initRaise(R->value); + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT. { + X = new SqliteExpr(); + X->initId(*(N1), QString::null, QString::null); + delete N1; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= nm(N1) DOT nm(N2) DOT. { + X = new SqliteExpr(); + X->initId(*(N1), *(N2), QString::null); + delete N1; + delete N2; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= expr(E1) not_opt(N) BETWEEN + expr(E2). [BETWEEN] { + X = new SqliteExpr(); + delete N; + delete E1; + delete E2; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= CASE case_operand(O) + case_exprlist(L) + case_else(E). { + X = new SqliteExpr(); + delete L; + delete O; + delete E; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= expr(E) not_opt(N) IN LP + exprlist(L). [IN] { + X = new SqliteExpr(); + delete N; + delete L; + delete E; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + +exprx ::= expr not_opt IN ID_DB. [IN] {} +exprx ::= expr not_opt IN nm DOT + ID_TAB. [IN] {} +exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN. {} +exprx ::= nm DOT ID_TAB|ID_COL. {} +exprx ::= nm DOT nm DOT ID_COL. {} +exprx ::= RAISE LP raisetype COMMA + ID_ERR_MSG RP. {} + +%type expr {SqliteExpr*} +%destructor expr {delete $$;} +expr(X) ::= exprx(E). {X = E;} +expr(X) ::= . { + X = new SqliteExpr(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } +%type not_opt {bool*} +%destructor not_opt {delete $$;} +not_opt(X) ::= . {X = new bool(false);} +not_opt(X) ::= NOT. {X = new bool(true);} + +%type likeop {SqliteExpr::LikeOp*} +%destructor likeop {delete $$;} +likeop(X) ::= LIKE|GLOB(T). {X = new SqliteExpr::LikeOp(SqliteExpr::likeOp(T->value));} + +%type case_exprlist {ParserExprList*} +%destructor case_exprlist {delete $$;} +case_exprlist(X) ::= case_exprlist(L) WHEN + expr(E1) THEN expr(E2). { + L->append(E1); + L->append(E2); + X = L; + } +case_exprlist(X) ::= WHEN expr(E1) THEN + expr(E2). { + X = new ParserExprList(); + X->append(E1); + X->append(E2); + } + +%type case_else {SqliteExpr*} +%destructor case_else {delete $$;} +case_else(X) ::= ELSE expr(E). {X = E;} +case_else(X) ::= . {X = nullptr;} + +%type case_operand {SqliteExpr*} +%destructor case_operand {delete $$;} +case_operand(X) ::= exprx(E). {X = E;} +case_operand(X) ::= . {X = nullptr;} + +%type exprlist {ParserExprList*} +%destructor exprlist {delete $$;} +exprlist(X) ::= nexprlist(L). {X = L;} +exprlist(X) ::= . {X = new ParserExprList();} + +%type nexprlist {ParserExprList*} +%destructor nexprlist {delete $$;} +nexprlist(X) ::= nexprlist(L) COMMA + expr(E). { + L->append(E); + X = L; + DONT_INHERIT_TOKENS("nexprlist"); + } +nexprlist(X) ::= exprx(E). { + X = new ParserExprList(); + X->append(E); + } + +///////////////////////////// The CREATE INDEX command /////////////////////// + +cmd(X) ::= CREATE uniqueflag(U) INDEX + nm(N1) ON nm(N2) dbnm(N3) + LP idxlist(L) RP onconf(C). { + X = new SqliteCreateIndex( + *(U), + false, + *(N1), + *(N2), + *(N3), + *(L), + *(C) + ); + delete U; + delete N1; + delete N2; + delete N3; + delete L; + delete C; + objectForTokens = X; + } + +cmd ::= CREATE uniqueflag INDEX nm dbnm + ON ID_TAB. {} +cmd ::= CREATE uniqueflag INDEX nm DOT + ID_IDX_NEW. {} +cmd ::= CREATE uniqueflag INDEX + ID_DB|ID_IDX_NEW. {} + +%type uniqueflag {bool*} +%destructor uniqueflag {delete $$;} +uniqueflag(X) ::= UNIQUE. {X = new bool(true);} +uniqueflag(X) ::= . {X = new bool(false);} + +%type idxlist_opt {ParserIndexedColumnList*} +%destructor idxlist_opt {delete $$;} +idxlist_opt(X) ::= . {X = new ParserIndexedColumnList();} +idxlist_opt(X) ::= LP idxlist(I) RP. {X = I;} + +%type idxlist {ParserIndexedColumnList*} +%destructor idxlist {delete $$;} +idxlist(X) ::= idxlist(L) COMMA + idxlist_single(S). { + L->append(S); + X = L; + DONT_INHERIT_TOKENS("idxlist"); + } +idxlist(X) ::= idxlist_single(S). { + X = new ParserIndexedColumnList(); + X->append(S); + } + +%type idxlist_single {SqliteIndexedColumn*} +%destructor idxlist_single {delete $$;} +idxlist_single(X) ::= nm(N) sortorder(S). { + SqliteIndexedColumn* obj = + new SqliteIndexedColumn( + *(N), + QString::null, + *(S) + ); + X = obj; + delete S; + delete N; + objectForTokens = X; + } + +idxlist_single ::= ID_COL. {} + +///////////////////////////// The DROP INDEX command ///////////////////////// + +cmd(X) ::= DROP INDEX fullname(N). { + X = new SqliteDropIndex(false, N->name1, N->name2); + delete N; + objectForTokens = X; + } + +cmd ::= DROP INDEX nm DOT ID_IDX. {} +cmd ::= DROP INDEX ID_DB|ID_IDX. {} + +///////////////////////////// The COPY command /////////////////////////////// + +cmd(X) ::= COPY orconf(C) nm(N1) dbnm(N2) + FROM nm(N3) USING DELIMITERS + STRING(S). { + X = new SqliteCopy( + *(C), + *(N1), + *(N2), + *(N3), + S->value + ); + delete C; + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } +cmd(X) ::= COPY orconf(C) nm(N1) dbnm(N2) + FROM nm(N3). { + X = new SqliteCopy( + *(C), + *(N1), + *(N2), + *(N3) + ); + delete C; + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } + +///////////////////////////// The VACUUM command ///////////////////////////// + +cmd(X) ::= VACUUM. { + X = new SqliteVacuum(); + objectForTokens = X; + } +cmd(X) ::= VACUUM nm(N). { + X = new SqliteVacuum(*(N)); + delete N; + objectForTokens = X; + } + +///////////////////////////// The PRAGMA command ///////////////////////////// + +cmd(X) ::= PRAGMA ids(I). { + X = new SqlitePragma(*(I), QString::null); + delete I; + objectForTokens = X; + } + +cmd(X) ::= PRAGMA nm(N) EQ nmnum(V). { + X = new SqlitePragma(*(N), QString::null, *(V), true); + delete N; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N) LP nmnum(V) RP. { + X = new SqlitePragma(*(N), QString::null, *(V), false); + delete N; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N) EQ minus_num(V). { + X = new SqlitePragma(*(N), QString::null, *(V), true); + delete N; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N) LP minus_num(V) RP. { + X = new SqlitePragma(*(N), QString::null, *(V), false); + delete N; + delete V; + objectForTokens = X; + } + +cmd ::= PRAGMA nm DOT ID_PRAGMA. {} +cmd ::= PRAGMA ID_DB|ID_PRAGMA. {} + +%type nmnum {QVariant*} +%destructor nmnum {delete $$;} +nmnum(X) ::= plus_num(N). {X = N;} +nmnum(X) ::= nm(N). { + X = new QVariant(*(N)); + delete N; + } +nmnum(X) ::= ON(T). {X = new QVariant(T->value);} +nmnum(X) ::= DELETE(T). {X = new QVariant(T->value);} +nmnum(X) ::= DEFAULT(T). {X = new QVariant(T->value);} + +%type plus_num {QVariant*} +%destructor plus_num {delete $$;} +plus_num(X) ::= PLUS number(N). {X = N;} +plus_num(X) ::= number(N). {X = N;} + +%type minus_num {QVariant*} +%destructor minus_num {delete $$;} +minus_num(X) ::= MINUS number(N). { + if (N->type() == QVariant::Double) + *(N) = -(N->toDouble()); + else if (N->type() == QVariant::LongLong) + *(N) = -(N->toLongLong()); + else + Q_ASSERT_X(true, "producing minus number", "QVariant is neither of Double or LongLong."); + + X = N; + } + +%type number {QVariant*} +%destructor number {delete $$;} +number(X) ::= INTEGER(N). {X = new QVariant(QVariant(N->value).toLongLong());} +number(X) ::= FLOAT(N). {X = new QVariant(QVariant(N->value).toDouble());} + +//////////////////////////// The CREATE TRIGGER command ///////////////////// + +cmd(X) ::= CREATE temp(T) TRIGGER + nm(N) trigger_time(TT) + trigger_event(EV) ON nm(N1) + dbnm(N2) foreach_clause(FC) + when_clause(WC) BEGIN + trigger_cmd_list(CL) END. { + X = new SqliteCreateTrigger( + *(T), + false, + *(N), + *(N1), + *(N2), + *(TT), + EV, + *(FC), + WC, + *(CL), + 2 + ); + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + delete CL; + objectForTokens = X; + } + +// Support full parsing when no BEGIN and END are present (for completion helper) +cmd(X) ::= CREATE temp(T) TRIGGER + nm(N) trigger_time(TT) + trigger_event(EV) ON nm(N1) + dbnm(N2) foreach_clause(FC) + when_clause(WC). { + QList CL; + + X = new SqliteCreateTrigger( + *(T), + false, + *(N), + *(N1), + *(N2), + *(TT), + EV, + *(FC), + WC, + CL, + 2 + ); + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +// Support full parsing when no END is present (for completion helper) +cmd(X) ::= CREATE temp(T) TRIGGER + nm(N) trigger_time(TT) + trigger_event(EV) ON nm(N1) + dbnm(N2) foreach_clause(FC) + when_clause(WC) BEGIN + trigger_cmd_list(CL). { + X = new SqliteCreateTrigger( + *(T), + false, + *(N), + *(N1), + *(N2), + *(TT), + EV, + *(FC), + WC, + *(CL), + 2 + ); + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + delete CL; + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +cmd ::= CREATE temp TRIGGER nm + trigger_time trigger_event + ON ID_TAB|ID_DB. {} +cmd ::= CREATE temp TRIGGER nm + trigger_time trigger_event + ON nm DOT ID_TAB. {} +cmd ::= CREATE temp TRIGGER ID_TRIG_NEW. {} + +%type trigger_time {SqliteCreateTrigger::Time*} +%destructor trigger_time {delete $$;} +trigger_time(X) ::= BEFORE. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::BEFORE);} +trigger_time(X) ::= AFTER. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::AFTER);} +trigger_time(X) ::= INSTEAD OF. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::INSTEAD_OF);} +trigger_time(X) ::= . {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::null);} + +%type trigger_event {SqliteCreateTrigger::Event*} +%destructor trigger_event {delete $$;} +trigger_event(X) ::= DELETE. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::DELETE); + objectForTokens = X; + } +trigger_event(X) ::= INSERT. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::INSERT); + objectForTokens = X; + } +trigger_event(X) ::= UPDATE. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::UPDATE); + objectForTokens = X; + } +trigger_event(X) ::= UPDATE OF + inscollist(L). { + X = new SqliteCreateTrigger::Event(*(L)); + delete L; + objectForTokens = X; + } + +%type foreach_clause {SqliteCreateTrigger::Scope*} +%destructor foreach_clause {delete $$;} +foreach_clause(X) ::= . {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::null);} +foreach_clause(X) ::= FOR EACH ROW. {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_ROW);} +foreach_clause(X) ::= FOR EACH STATEMENT. {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT);} + +%type when_clause {SqliteExpr*} +%destructor when_clause {if ($$) delete $$;} +when_clause(X) ::= . {X = nullptr;} +when_clause(X) ::= WHEN expr(E). {X = E;} + +%type trigger_cmd_list {ParserQueryList*} +%destructor trigger_cmd_list {delete $$;} +trigger_cmd_list(X) ::= trigger_cmd_list(L) + trigger_cmd(C) SEMI. { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("trigger_cmd_list"); + } +trigger_cmd_list(X) ::= trigger_cmd(C) + SEMI. { + X = new ParserQueryList(); + X->append(C); + } + +%type trigger_cmd {SqliteQuery*} +%destructor trigger_cmd {delete $$;} +trigger_cmd(X) ::= update_stmt(S). {X = S;} +trigger_cmd(X) ::= insert_stmt(S). {X = S;} +trigger_cmd(X) ::= delete_stmt(S). {X = S;} +trigger_cmd(X) ::= select_stmt(S). {X = S;} + +%type raisetype {Token*} +raisetype(X) ::= ROLLBACK|ABORT|FAIL(V). {X = V;} + +//////////////////////// DROP TRIGGER statement ////////////////////////////// +cmd(X) ::= DROP TRIGGER fullname(N). { + X = new SqliteDropTrigger(false, N->name1, N->name2); + delete N; + objectForTokens = X; + } + +cmd ::= DROP TRIGGER nm DOT ID_TRIG. {} +cmd ::= DROP TRIGGER ID_DB|ID_TRIG. {} + +//////////////////////// ATTACH DATABASE file AS name ///////////////////////// +cmd(X) ::= ATTACH database_kw_opt(D) + ids(I1) AS ids(I2) key_opt(K). { + SqliteExpr* e1 = new SqliteExpr(); + SqliteExpr* e2 = new SqliteExpr(); + e1->initLiteral(*(I1)); + e2->initLiteral(*(I2)); + X = new SqliteAttach(*(D), e1, e2, K); + delete D; + delete I1; + delete I2; + objectForTokens = X; + } + +%type key_opt {SqliteExpr*} +%destructor key_opt {if ($$) delete $$;} +key_opt(X) ::= . {X = nullptr;} +key_opt(X) ::= USING ids(I). { + SqliteExpr* e = new SqliteExpr(); + e->initLiteral(*(I)); + delete I; + X = e; + } + +%type database_kw_opt {bool*} +%destructor database_kw_opt {delete $$;} +database_kw_opt(X) ::= DATABASE. {X = new bool(true);} +database_kw_opt(X) ::= . {X = new bool(false);} + + +//////////////////////// DETACH DATABASE name ///////////////////////////////// +cmd(X) ::= DETACH database_kw_opt(D) + nm(N). { + SqliteExpr* e = new SqliteExpr(); + e->initLiteral(*(N)); + delete N; + X = new SqliteDetach(*(D), e); + delete D; + objectForTokens = X; + } diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.cpp new file mode 100644 index 0000000..3bdd9a4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.cpp @@ -0,0 +1,5262 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +** +** This version of "lempar.c" is modified, slightly, for use by SQLite. +** The only modifications are the addition of a couple of NEVER() +** macros to disable tests that are needed in the case of a general +** LALR(1) grammar but which are always false in the +** specific grammar used by SQLite. +*/ +/* First off, code is included that follows the "include" declaration +** in the input grammar file. */ +#include + +#include "token.h" +#include "parsercontext.h" +#include "parser_helper_stubs.h" +#include "common/utils_sql.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser/ast/sqlitewith.h" +#include +#include + +#define assert(X) Q_ASSERT(X) +#define UNUSED_PARAMETER(X) (void)(X) +#define DONT_INHERIT_TOKENS(X) noTokenInheritanceFields << X +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** sqlite3_parseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is sqlite3_parseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** sqlite3_parseARG_SDECL A static variable declaration for the %extra_argument +** sqlite3_parseARG_PDECL A parameter declaration for the %extra_argument +** sqlite3_parseARG_STORE Code to store %extra_argument into yypParser +** sqlite3_parseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +#define YYCODETYPE unsigned short int +#define YYNOCODE 278 +#define YYACTIONTYPE unsigned short int +#define YYWILDCARD 61 +#define sqlite3_parseTOKENTYPE Token* +typedef union { + int yyinit; + sqlite3_parseTOKENTYPE yy0; + SqliteCreateTable::Column::Constraint* yy4; + SqliteCreateTable::Constraint* yy8; + ParserExprList* yy13; + QVariant* yy21; + ParserStubAlias* yy28; + SqliteConflictAlgo* yy30; + ParserFullName* yy66; + ParserCreateTableConstraintList* yy87; + SqliteIndexedColumn* yy90; + ParserFkConditionList* yy108; + SqliteSelect::Core::JoinConstraint* yy117; + ParserCreateTableColumnList* yy118; + SqliteSelect* yy123; + SqliteLimit* yy128; + ParserDeferSubClause* yy131; + ParserIndexedColumnList* yy139; + SqliteCreateTrigger::Time* yy152; + SqliteSelect::CompoundOperator* yy168; + SqliteSelect::Core::SingleSource* yy173; + QString* yy211; + ParserQueryList* yy214; + ParserStubExplain* yy225; + SqliteSortOrder* yy226; + bool* yy237; + ParserStubInsertOrReplace* yy250; + ParserResultColumnList* yy263; + SqliteForeignKey::Condition* yy271; + SqliteColumnType* yy299; + ParserStubTransDetails* yy300; + SqliteCreateTrigger::Event* yy309; + SqliteForeignKey::Condition::Reaction* yy312; + ParserOtherSourceList* yy359; + SqliteWith* yy367; + SqliteSelect::Core::JoinSource* yy373; + SqliteExpr::LikeOp* yy374; + int* yy376; + ParserSetValueList* yy381; + SqliteQuery* yy399; + SqliteCreateTrigger::Scope* yy409; + ParserExprNestedList* yy416; + SqliteCreateTable::Column* yy425; + ParserStringList* yy445; + ParserCreateTableColumnConstraintList* yy449; + SqliteSelect::Core* yy468; + ParserIndexedBy* yy472; + SqliteSelect::Core::JoinOp* yy473; + SqliteExpr* yy490; + ParserOrderByList* yy495; + SqliteInitially* yy498; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define sqlite3_parseARG_SDECL ParserContext* parserContext; +#define sqlite3_parseARG_PDECL ,ParserContext* parserContext +#define sqlite3_parseARG_FETCH ParserContext* parserContext = yypParser->parserContext +#define sqlite3_parseARG_STORE yypParser->parserContext = parserContext +#define YYNSTATE 724 +#define YYNRULE 424 +#define YYFALLBACK 1 +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +#define GET_CONTEXT yyParser* yypParser = pParser; sqlite3_parseARG_FETCH + +/* The yyzerominor constant is used to initialize instances of +** YYMINORTYPE objects to zero. */ +static const YYMINORTYPE yyzerominor = { 0 }; + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +#define YY_ACTTAB_COUNT (2221) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 431, 49, 49, 48, 48, 48, 47, 216, 716, 339, + /* 10 */ 643, 425, 52, 52, 52, 52, 45, 50, 50, 50, + /* 20 */ 50, 49, 49, 48, 48, 48, 47, 216, 721, 1026, + /* 30 */ 1026, 643, 131, 580, 52, 52, 52, 52, 411, 50, + /* 40 */ 50, 50, 50, 49, 49, 48, 48, 48, 47, 216, + /* 50 */ 579, 81, 58, 643, 157, 685, 301, 282, 1026, 1026, + /* 60 */ 42, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + /* 70 */ 1026, 1026, 563, 1026, 1026, 1026, 1026, 39, 40, 1026, + /* 80 */ 1026, 1026, 1026, 1026, 41, 431, 528, 385, 716, 595, + /* 90 */ 594, 280, 4, 377, 716, 630, 425, 642, 608, 422, + /* 100 */ 12, 134, 687, 429, 562, 609, 483, 690, 331, 279, + /* 110 */ 714, 713, 564, 565, 642, 689, 688, 687, 235, 506, + /* 120 */ 60, 320, 610, 411, 48, 48, 48, 47, 216, 122, + /* 130 */ 243, 213, 247, 59, 1142, 1142, 486, 609, 607, 603, + /* 140 */ 685, 306, 485, 584, 716, 42, 507, 509, 642, 508, + /* 150 */ 676, 9, 642, 144, 95, 281, 379, 276, 378, 132, + /* 160 */ 297, 716, 39, 40, 601, 200, 199, 7, 355, 41, + /* 170 */ 884, 307, 1134, 274, 249, 716, 17, 4, 884, 1134, + /* 180 */ 57, 717, 642, 431, 422, 884, 329, 687, 429, 716, + /* 190 */ 687, 643, 690, 687, 425, 690, 714, 713, 690, 642, + /* 200 */ 689, 688, 687, 689, 688, 687, 689, 688, 687, 98, + /* 210 */ 682, 240, 643, 218, 410, 884, 486, 884, 884, 483, + /* 220 */ 716, 411, 239, 884, 303, 582, 512, 581, 884, 884, + /* 230 */ 884, 884, 884, 642, 643, 676, 9, 642, 685, 217, + /* 240 */ 245, 673, 102, 42, 287, 300, 714, 713, 67, 302, + /* 250 */ 148, 307, 1133, 151, 306, 484, 81, 715, 97, 1133, + /* 260 */ 39, 40, 551, 714, 713, 771, 130, 41, 946, 376, + /* 270 */ 373, 372, 447, 47, 216, 4, 946, 714, 713, 334, + /* 280 */ 642, 682, 422, 946, 606, 687, 429, 371, 448, 447, + /* 290 */ 690, 714, 713, 304, 265, 146, 267, 642, 689, 688, + /* 300 */ 687, 287, 68, 677, 691, 255, 362, 259, 359, 692, + /* 310 */ 1027, 1027, 682, 946, 715, 946, 946, 447, 698, 234, + /* 320 */ 386, 715, 714, 713, 773, 651, 946, 946, 946, 946, + /* 330 */ 110, 642, 317, 676, 9, 642, 222, 677, 299, 53, + /* 340 */ 54, 426, 289, 1027, 1027, 675, 675, 51, 51, 52, + /* 350 */ 52, 52, 52, 716, 50, 50, 50, 50, 49, 49, + /* 360 */ 48, 48, 48, 47, 216, 431, 428, 340, 716, 335, + /* 370 */ 671, 670, 287, 283, 716, 138, 425, 209, 219, 430, + /* 380 */ 268, 395, 651, 682, 336, 715, 715, 686, 186, 53, + /* 390 */ 54, 426, 289, 715, 452, 675, 675, 51, 51, 52, + /* 400 */ 52, 52, 52, 411, 50, 50, 50, 50, 49, 49, + /* 410 */ 48, 48, 48, 47, 216, 91, 953, 716, 619, 712, + /* 420 */ 685, 403, 382, 130, 710, 42, 376, 373, 372, 711, + /* 430 */ 233, 953, 394, 311, 210, 593, 666, 384, 428, 16, + /* 440 */ 316, 659, 39, 40, 371, 231, 230, 716, 89, 41, + /* 450 */ 931, 430, 716, 658, 716, 714, 713, 4, 931, 686, + /* 460 */ 92, 143, 642, 358, 422, 931, 674, 687, 429, 14, + /* 470 */ 714, 713, 690, 131, 456, 551, 714, 713, 953, 642, + /* 480 */ 689, 688, 687, 668, 667, 210, 593, 458, 384, 457, + /* 490 */ 576, 88, 1027, 1027, 13, 931, 672, 931, 931, 55, + /* 500 */ 575, 678, 43, 368, 38, 401, 36, 381, 931, 1, + /* 510 */ 931, 931, 641, 642, 634, 676, 9, 642, 661, 714, + /* 520 */ 713, 53, 54, 426, 289, 1027, 1027, 675, 675, 51, + /* 530 */ 51, 52, 52, 52, 52, 660, 50, 50, 50, 50, + /* 540 */ 49, 49, 48, 48, 48, 47, 216, 657, 648, 714, + /* 550 */ 713, 496, 542, 569, 714, 713, 714, 713, 656, 691, + /* 560 */ 543, 614, 320, 30, 692, 27, 716, 585, 274, 682, + /* 570 */ 160, 1027, 1027, 426, 289, 693, 613, 675, 675, 51, + /* 580 */ 51, 52, 52, 52, 52, 398, 50, 50, 50, 50, + /* 590 */ 49, 49, 48, 48, 48, 47, 216, 1025, 1025, 81, + /* 600 */ 53, 54, 426, 289, 1027, 1027, 675, 675, 51, 51, + /* 610 */ 52, 52, 52, 52, 496, 50, 50, 50, 50, 49, + /* 620 */ 49, 48, 48, 48, 47, 216, 1025, 1025, 1025, 1025, + /* 630 */ 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, + /* 640 */ 716, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, + /* 650 */ 1025, 1025, 1027, 1027, 357, 50, 50, 50, 50, 49, + /* 660 */ 49, 48, 48, 48, 47, 216, 288, 552, 714, 713, + /* 670 */ 495, 682, 298, 662, 346, 153, 538, 69, 694, 715, + /* 680 */ 715, 53, 54, 426, 289, 1027, 1027, 675, 675, 51, + /* 690 */ 51, 52, 52, 52, 52, 1094, 50, 50, 50, 50, + /* 700 */ 49, 49, 48, 48, 48, 47, 216, 53, 54, 426, + /* 710 */ 289, 418, 511, 675, 675, 51, 51, 52, 52, 52, + /* 720 */ 52, 159, 50, 50, 50, 50, 49, 49, 48, 48, + /* 730 */ 48, 47, 216, 490, 954, 315, 482, 482, 663, 553, + /* 740 */ 215, 650, 714, 713, 81, 53, 54, 426, 289, 954, + /* 750 */ 414, 675, 675, 51, 51, 52, 52, 52, 52, 397, + /* 760 */ 50, 50, 50, 50, 49, 49, 48, 48, 48, 47, + /* 770 */ 216, 158, 1094, 21, 716, 627, 459, 716, 1079, 716, + /* 780 */ 647, 1045, 140, 89, 716, 1149, 154, 435, 2, 715, + /* 790 */ 424, 671, 670, 396, 460, 461, 954, 53, 54, 426, + /* 800 */ 289, 573, 716, 675, 675, 51, 51, 52, 52, 52, + /* 810 */ 52, 321, 50, 50, 50, 50, 49, 49, 48, 48, + /* 820 */ 48, 47, 216, 431, 1108, 81, 206, 205, 204, 53, + /* 830 */ 54, 426, 289, 716, 425, 675, 675, 51, 51, 52, + /* 840 */ 52, 52, 52, 344, 50, 50, 50, 50, 49, 49, + /* 850 */ 48, 48, 48, 47, 216, 597, 715, 666, 600, 462, + /* 860 */ 666, 411, 31, 716, 657, 90, 12, 894, 720, 668, + /* 870 */ 667, 609, 724, 434, 81, 656, 714, 713, 685, 714, + /* 880 */ 713, 714, 713, 42, 528, 272, 714, 713, 610, 349, + /* 890 */ 528, 450, 89, 677, 12, 633, 633, 338, 636, 609, + /* 900 */ 39, 40, 649, 609, 714, 713, 716, 41, 1142, 1142, + /* 910 */ 716, 524, 682, 581, 716, 4, 610, 468, 60, 450, + /* 920 */ 642, 208, 422, 506, 60, 687, 429, 677, 33, 109, + /* 930 */ 690, 609, 500, 501, 352, 714, 713, 642, 689, 688, + /* 940 */ 687, 428, 900, 900, 467, 466, 552, 465, 421, 383, + /* 950 */ 507, 509, 142, 508, 430, 440, 69, 1142, 1142, 715, + /* 960 */ 444, 722, 686, 182, 646, 714, 713, 645, 231, 230, + /* 970 */ 437, 642, 356, 676, 9, 642, 417, 444, 53, 54, + /* 980 */ 426, 289, 91, 91, 675, 675, 51, 51, 52, 52, + /* 990 */ 52, 52, 644, 50, 50, 50, 50, 49, 49, 48, + /* 1000 */ 48, 48, 47, 216, 1034, 444, 668, 667, 714, 713, + /* 1010 */ 91, 453, 714, 713, 682, 641, 714, 713, 324, 202, + /* 1020 */ 53, 54, 426, 289, 446, 680, 675, 675, 51, 51, + /* 1030 */ 52, 52, 52, 52, 639, 50, 50, 50, 50, 49, + /* 1040 */ 49, 48, 48, 48, 47, 216, 605, 53, 54, 426, + /* 1050 */ 289, 716, 446, 675, 675, 51, 51, 52, 52, 52, + /* 1060 */ 52, 459, 50, 50, 50, 50, 49, 49, 48, 48, + /* 1070 */ 48, 47, 216, 453, 715, 37, 663, 423, 215, 460, + /* 1080 */ 341, 369, 592, 53, 54, 426, 289, 638, 89, 675, + /* 1090 */ 675, 51, 51, 52, 52, 52, 52, 31, 50, 50, + /* 1100 */ 50, 50, 49, 49, 48, 48, 48, 47, 216, 413, + /* 1110 */ 723, 2, 11, 53, 54, 426, 289, 34, 588, 675, + /* 1120 */ 675, 51, 51, 52, 52, 52, 52, 624, 50, 50, + /* 1130 */ 50, 50, 49, 49, 48, 48, 48, 47, 216, 515, + /* 1140 */ 715, 537, 29, 91, 342, 666, 140, 8, 571, 53, + /* 1150 */ 54, 426, 289, 714, 713, 675, 675, 51, 51, 52, + /* 1160 */ 52, 52, 52, 548, 50, 50, 50, 50, 49, 49, + /* 1170 */ 48, 48, 48, 47, 216, 91, 252, 234, 386, 53, + /* 1180 */ 54, 426, 289, 89, 271, 675, 675, 51, 51, 52, + /* 1190 */ 52, 52, 52, 333, 50, 50, 50, 50, 49, 49, + /* 1200 */ 48, 48, 48, 47, 216, 532, 81, 682, 696, 338, + /* 1210 */ 87, 53, 54, 426, 289, 22, 557, 675, 675, 51, + /* 1220 */ 51, 52, 52, 52, 52, 615, 50, 50, 50, 50, + /* 1230 */ 49, 49, 48, 48, 48, 47, 216, 682, 1109, 91, + /* 1240 */ 504, 716, 53, 54, 426, 289, 604, 137, 675, 675, + /* 1250 */ 51, 51, 52, 52, 52, 52, 136, 50, 50, 50, + /* 1260 */ 50, 49, 49, 48, 48, 48, 47, 216, 431, 1107, + /* 1270 */ 135, 488, 388, 722, 53, 54, 426, 289, 308, 425, + /* 1280 */ 675, 675, 51, 51, 52, 52, 52, 52, 620, 50, + /* 1290 */ 50, 50, 50, 49, 49, 48, 48, 48, 47, 216, + /* 1300 */ 428, 552, 624, 428, 283, 716, 411, 517, 404, 682, + /* 1310 */ 697, 69, 829, 430, 715, 715, 430, 715, 464, 540, + /* 1320 */ 653, 686, 186, 685, 686, 186, 99, 428, 42, 716, + /* 1330 */ 390, 715, 226, 223, 599, 539, 5, 534, 534, 682, + /* 1340 */ 430, 91, 91, 714, 713, 39, 40, 682, 686, 186, + /* 1350 */ 555, 431, 41, 620, 620, 403, 405, 598, 403, 387, + /* 1360 */ 4, 570, 425, 629, 311, 642, 428, 422, 326, 10, + /* 1370 */ 687, 429, 716, 624, 316, 690, 716, 316, 428, 430, + /* 1380 */ 716, 620, 642, 689, 688, 687, 715, 686, 186, 411, + /* 1390 */ 716, 430, 498, 596, 716, 632, 499, 602, 612, 686, + /* 1400 */ 186, 406, 491, 591, 285, 631, 685, 714, 713, 266, + /* 1410 */ 590, 42, 716, 365, 209, 589, 642, 350, 676, 9, + /* 1420 */ 642, 332, 715, 261, 107, 611, 530, 221, 39, 40, + /* 1430 */ 225, 714, 713, 399, 431, 41, 715, 716, 283, 351, + /* 1440 */ 316, 138, 716, 4, 270, 425, 209, 286, 642, 682, + /* 1450 */ 422, 715, 316, 714, 429, 716, 209, 530, 690, 716, + /* 1460 */ 715, 19, 561, 491, 716, 642, 689, 688, 687, 525, + /* 1470 */ 375, 65, 411, 519, 714, 713, 273, 475, 714, 713, + /* 1480 */ 64, 370, 714, 713, 619, 18, 525, 514, 129, 685, + /* 1490 */ 519, 479, 714, 713, 42, 716, 714, 713, 227, 642, + /* 1500 */ 428, 676, 9, 642, 514, 63, 286, 428, 479, 475, + /* 1510 */ 622, 39, 40, 430, 714, 713, 431, 718, 41, 715, + /* 1520 */ 430, 686, 181, 715, 279, 716, 4, 425, 686, 163, + /* 1530 */ 715, 642, 366, 422, 472, 156, 687, 429, 533, 714, + /* 1540 */ 713, 690, 125, 3, 714, 713, 416, 79, 642, 689, + /* 1550 */ 688, 687, 469, 85, 411, 258, 438, 714, 713, 220, + /* 1560 */ 26, 714, 713, 25, 706, 407, 714, 713, 715, 77, + /* 1570 */ 704, 685, 510, 438, 641, 516, 42, 715, 716, 472, + /* 1580 */ 353, 641, 642, 715, 676, 9, 642, 503, 1035, 428, + /* 1590 */ 625, 536, 505, 39, 40, 83, 119, 714, 713, 428, + /* 1600 */ 41, 621, 430, 497, 428, 420, 254, 62, 4, 1037, + /* 1610 */ 686, 172, 430, 642, 140, 422, 469, 430, 687, 429, + /* 1620 */ 686, 189, 152, 690, 337, 686, 187, 714, 713, 251, + /* 1630 */ 642, 689, 688, 687, 161, 54, 426, 289, 716, 558, + /* 1640 */ 675, 675, 51, 51, 52, 52, 52, 52, 478, 50, + /* 1650 */ 50, 50, 50, 49, 49, 48, 48, 48, 47, 216, + /* 1660 */ 431, 428, 455, 641, 642, 705, 676, 9, 642, 111, + /* 1670 */ 716, 425, 428, 641, 430, 428, 702, 454, 641, 428, + /* 1680 */ 714, 713, 686, 196, 207, 430, 428, 72, 430, 715, + /* 1690 */ 428, 436, 430, 686, 195, 716, 686, 197, 411, 430, + /* 1700 */ 686, 201, 716, 430, 428, 699, 428, 686, 232, 428, + /* 1710 */ 209, 686, 290, 96, 203, 685, 150, 430, 715, 430, + /* 1720 */ 42, 224, 430, 86, 319, 686, 190, 686, 194, 428, + /* 1730 */ 686, 193, 257, 82, 695, 641, 256, 39, 40, 318, + /* 1740 */ 714, 713, 430, 719, 41, 715, 641, 715, 149, 641, + /* 1750 */ 686, 185, 4, 641, 707, 679, 287, 642, 709, 422, + /* 1760 */ 641, 567, 687, 429, 641, 15, 428, 690, 715, 715, + /* 1770 */ 367, 428, 714, 713, 642, 689, 688, 687, 641, 430, + /* 1780 */ 641, 716, 703, 641, 430, 147, 287, 686, 188, 701, + /* 1790 */ 708, 328, 686, 314, 428, 432, 428, 714, 713, 715, + /* 1800 */ 700, 428, 145, 641, 714, 713, 408, 430, 642, 430, + /* 1810 */ 676, 9, 642, 428, 430, 686, 313, 686, 312, 715, + /* 1820 */ 428, 327, 686, 184, 684, 428, 430, 433, 494, 294, + /* 1830 */ 428, 637, 234, 430, 686, 171, 428, 651, 430, 17, + /* 1840 */ 641, 686, 170, 430, 715, 641, 686, 183, 293, 430, + /* 1850 */ 428, 686, 169, 626, 716, 400, 428, 686, 192, 292, + /* 1860 */ 428, 287, 291, 430, 28, 44, 715, 651, 641, 430, + /* 1870 */ 641, 686, 191, 430, 715, 641, 428, 686, 168, 428, + /* 1880 */ 402, 686, 167, 714, 713, 56, 716, 641, 683, 430, + /* 1890 */ 428, 216, 430, 428, 641, 428, 325, 686, 93, 641, + /* 1900 */ 686, 166, 716, 430, 641, 428, 430, 428, 430, 640, + /* 1910 */ 641, 686, 164, 66, 686, 174, 686, 173, 430, 716, + /* 1920 */ 430, 716, 428, 616, 641, 716, 686, 175, 686, 178, + /* 1930 */ 641, 229, 419, 214, 641, 430, 228, 415, 428, 716, + /* 1940 */ 35, 428, 651, 686, 94, 617, 716, 635, 141, 716, + /* 1950 */ 641, 430, 428, 641, 430, 428, 714, 713, 715, 686, + /* 1960 */ 177, 139, 686, 176, 641, 430, 108, 641, 430, 641, + /* 1970 */ 428, 133, 392, 686, 180, 716, 686, 179, 716, 641, + /* 1980 */ 716, 641, 531, 430, 715, 430, 716, 583, 714, 713, + /* 1990 */ 389, 686, 165, 686, 70, 393, 641, 716, 248, 716, + /* 2000 */ 541, 716, 481, 246, 714, 713, 380, 477, 715, 716, + /* 2010 */ 386, 715, 641, 578, 716, 641, 715, 716, 577, 716, + /* 2020 */ 330, 714, 713, 714, 713, 275, 641, 714, 713, 641, + /* 2030 */ 244, 716, 242, 502, 473, 527, 470, 277, 715, 523, + /* 2040 */ 574, 714, 713, 715, 641, 715, 641, 573, 714, 713, + /* 2050 */ 310, 714, 713, 236, 568, 549, 269, 322, 572, 554, + /* 2060 */ 518, 263, 529, 492, 547, 546, 715, 715, 715, 715, + /* 2070 */ 545, 489, 360, 347, 715, 544, 309, 714, 713, 128, + /* 2080 */ 714, 713, 714, 713, 442, 715, 715, 521, 714, 713, + /* 2090 */ 80, 127, 480, 427, 106, 284, 262, 715, 535, 714, + /* 2100 */ 713, 714, 713, 714, 713, 441, 715, 212, 715, 476, + /* 2110 */ 126, 714, 713, 443, 354, 648, 714, 713, 24, 714, + /* 2120 */ 713, 714, 713, 264, 253, 363, 526, 250, 474, 241, + /* 2130 */ 237, 238, 124, 714, 713, 78, 715, 715, 105, 123, + /* 2140 */ 715, 121, 715, 715, 715, 84, 513, 155, 104, 348, + /* 2150 */ 120, 493, 103, 345, 118, 76, 343, 117, 471, 116, + /* 2160 */ 463, 75, 652, 115, 114, 623, 74, 323, 73, 113, + /* 2170 */ 23, 451, 20, 449, 101, 445, 100, 112, 439, 520, + /* 2180 */ 162, 61, 655, 295, 669, 412, 278, 198, 665, 569, + /* 2190 */ 618, 522, 374, 305, 6, 628, 364, 681, 664, 654, + /* 2200 */ 260, 361, 211, 556, 409, 71, 296, 566, 560, 559, + /* 2210 */ 487, 81, 587, 1150, 46, 586, 1150, 1150, 1150, 1150, + /* 2220 */ 550, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 4, 81, 82, 83, 84, 85, 86, 87, 4, 58, + /* 10 */ 5, 15, 72, 73, 74, 75, 76, 77, 78, 79, + /* 20 */ 80, 81, 82, 83, 84, 85, 86, 87, 89, 33, + /* 30 */ 34, 26, 34, 28, 72, 73, 74, 75, 42, 77, + /* 40 */ 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + /* 50 */ 45, 55, 96, 48, 98, 59, 93, 104, 62, 63, + /* 60 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + /* 70 */ 74, 75, 14, 77, 78, 79, 80, 81, 82, 83, + /* 80 */ 84, 85, 86, 87, 88, 4, 185, 134, 4, 136, + /* 90 */ 137, 111, 96, 35, 4, 104, 15, 101, 104, 103, + /* 100 */ 96, 107, 106, 107, 46, 101, 61, 111, 64, 129, + /* 110 */ 106, 107, 110, 111, 118, 119, 120, 121, 167, 218, + /* 120 */ 219, 123, 118, 42, 83, 84, 85, 86, 87, 104, + /* 130 */ 51, 87, 53, 142, 138, 139, 61, 133, 144, 145, + /* 140 */ 59, 96, 97, 59, 4, 64, 245, 246, 152, 248, + /* 150 */ 154, 155, 156, 109, 110, 111, 112, 113, 114, 115, + /* 160 */ 97, 4, 81, 82, 83, 81, 82, 266, 267, 88, + /* 170 */ 89, 96, 97, 129, 95, 4, 151, 96, 97, 104, + /* 180 */ 96, 91, 101, 4, 103, 104, 128, 106, 107, 4, + /* 190 */ 106, 5, 111, 106, 15, 111, 106, 107, 111, 118, + /* 200 */ 119, 120, 121, 119, 120, 121, 119, 120, 121, 8, + /* 210 */ 4, 10, 26, 12, 28, 134, 61, 136, 137, 61, + /* 220 */ 4, 42, 21, 142, 23, 214, 45, 216, 147, 148, + /* 230 */ 149, 150, 151, 152, 48, 154, 155, 156, 59, 96, + /* 240 */ 161, 97, 41, 64, 177, 44, 106, 107, 104, 48, + /* 250 */ 49, 96, 97, 52, 96, 97, 55, 190, 57, 104, + /* 260 */ 81, 82, 105, 106, 107, 99, 109, 88, 89, 112, + /* 270 */ 113, 114, 101, 86, 87, 96, 97, 106, 107, 212, + /* 280 */ 101, 4, 103, 104, 144, 106, 107, 130, 117, 118, + /* 290 */ 111, 106, 107, 92, 51, 94, 53, 118, 119, 120, + /* 300 */ 121, 177, 96, 118, 135, 124, 125, 126, 177, 140, + /* 310 */ 33, 34, 106, 134, 190, 136, 137, 146, 102, 138, + /* 320 */ 139, 190, 106, 107, 99, 258, 147, 148, 149, 150, + /* 330 */ 99, 152, 131, 154, 155, 156, 212, 152, 95, 62, + /* 340 */ 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, + /* 350 */ 73, 74, 75, 4, 77, 78, 79, 80, 81, 82, + /* 360 */ 83, 84, 85, 86, 87, 4, 177, 166, 4, 194, + /* 370 */ 195, 196, 177, 177, 4, 98, 15, 252, 247, 190, + /* 380 */ 177, 185, 258, 106, 181, 190, 190, 198, 199, 62, + /* 390 */ 63, 64, 65, 190, 269, 68, 69, 70, 71, 72, + /* 400 */ 73, 74, 75, 42, 77, 78, 79, 80, 81, 82, + /* 410 */ 83, 84, 85, 86, 87, 219, 89, 4, 141, 19, + /* 420 */ 59, 232, 233, 109, 24, 64, 112, 113, 114, 29, + /* 430 */ 225, 104, 236, 237, 220, 221, 261, 223, 177, 234, + /* 440 */ 251, 97, 81, 82, 130, 81, 82, 4, 104, 88, + /* 450 */ 89, 190, 4, 258, 4, 106, 107, 96, 97, 198, + /* 460 */ 199, 99, 101, 249, 103, 104, 111, 106, 107, 71, + /* 470 */ 106, 107, 111, 34, 110, 105, 106, 107, 151, 118, + /* 480 */ 119, 120, 121, 119, 120, 220, 221, 123, 223, 125, + /* 490 */ 19, 43, 33, 34, 96, 134, 97, 136, 137, 40, + /* 500 */ 29, 152, 96, 64, 158, 244, 160, 83, 147, 96, + /* 510 */ 149, 150, 251, 152, 101, 154, 155, 156, 97, 106, + /* 520 */ 107, 62, 63, 64, 65, 66, 67, 68, 69, 70, + /* 530 */ 71, 72, 73, 74, 75, 97, 77, 78, 79, 80, + /* 540 */ 81, 82, 83, 84, 85, 86, 87, 9, 100, 106, + /* 550 */ 107, 101, 205, 206, 106, 107, 106, 107, 20, 135, + /* 560 */ 213, 118, 123, 104, 140, 123, 4, 190, 129, 4, + /* 570 */ 96, 33, 34, 64, 65, 198, 133, 68, 69, 70, + /* 580 */ 71, 72, 73, 74, 75, 143, 77, 78, 79, 80, + /* 590 */ 81, 82, 83, 84, 85, 86, 87, 33, 34, 55, + /* 600 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 610 */ 72, 73, 74, 75, 164, 77, 78, 79, 80, 81, + /* 620 */ 82, 83, 84, 85, 86, 87, 62, 63, 64, 65, + /* 630 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + /* 640 */ 4, 77, 78, 79, 80, 81, 82, 83, 84, 85, + /* 650 */ 86, 87, 33, 34, 110, 77, 78, 79, 80, 81, + /* 660 */ 82, 83, 84, 85, 86, 87, 177, 177, 106, 107, + /* 670 */ 50, 106, 182, 108, 54, 104, 186, 187, 116, 190, + /* 680 */ 190, 62, 63, 64, 65, 66, 67, 68, 69, 70, + /* 690 */ 71, 72, 73, 74, 75, 12, 77, 78, 79, 80, + /* 700 */ 81, 82, 83, 84, 85, 86, 87, 62, 63, 64, + /* 710 */ 65, 98, 23, 68, 69, 70, 71, 72, 73, 74, + /* 720 */ 75, 96, 77, 78, 79, 80, 81, 82, 83, 84, + /* 730 */ 85, 86, 87, 113, 89, 188, 100, 101, 191, 192, + /* 740 */ 193, 97, 106, 107, 55, 62, 63, 64, 65, 104, + /* 750 */ 98, 68, 69, 70, 71, 72, 73, 74, 75, 30, + /* 760 */ 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, + /* 770 */ 87, 96, 89, 38, 4, 97, 177, 4, 89, 4, + /* 780 */ 23, 161, 104, 104, 4, 170, 171, 172, 173, 190, + /* 790 */ 194, 195, 196, 64, 195, 196, 151, 62, 63, 64, + /* 800 */ 65, 122, 4, 68, 69, 70, 71, 72, 73, 74, + /* 810 */ 75, 179, 77, 78, 79, 80, 81, 82, 83, 84, + /* 820 */ 85, 86, 87, 4, 89, 55, 124, 125, 126, 62, + /* 830 */ 63, 64, 65, 4, 15, 68, 69, 70, 71, 72, + /* 840 */ 73, 74, 75, 177, 77, 78, 79, 80, 81, 82, + /* 850 */ 83, 84, 85, 86, 87, 97, 190, 261, 83, 260, + /* 860 */ 261, 42, 104, 4, 9, 98, 96, 97, 169, 119, + /* 870 */ 120, 101, 0, 174, 55, 20, 106, 107, 59, 106, + /* 880 */ 107, 106, 107, 64, 185, 97, 106, 107, 118, 257, + /* 890 */ 185, 118, 104, 118, 96, 33, 34, 25, 118, 101, + /* 900 */ 81, 82, 97, 133, 106, 107, 4, 88, 138, 139, + /* 910 */ 4, 214, 4, 216, 4, 96, 118, 218, 219, 146, + /* 920 */ 101, 96, 103, 218, 219, 106, 107, 152, 66, 67, + /* 930 */ 111, 133, 124, 125, 126, 106, 107, 118, 119, 120, + /* 940 */ 121, 177, 144, 145, 245, 246, 177, 248, 185, 185, + /* 950 */ 245, 246, 104, 248, 190, 186, 187, 138, 139, 190, + /* 960 */ 101, 89, 198, 199, 97, 106, 107, 97, 81, 82, + /* 970 */ 271, 152, 267, 154, 155, 156, 185, 118, 62, 63, + /* 980 */ 64, 65, 219, 219, 68, 69, 70, 71, 72, 73, + /* 990 */ 74, 75, 97, 77, 78, 79, 80, 81, 82, 83, + /* 1000 */ 84, 85, 86, 87, 158, 146, 119, 120, 106, 107, + /* 1010 */ 219, 101, 106, 107, 106, 251, 106, 107, 272, 273, + /* 1020 */ 62, 63, 64, 65, 118, 117, 68, 69, 70, 71, + /* 1030 */ 72, 73, 74, 75, 97, 77, 78, 79, 80, 81, + /* 1040 */ 82, 83, 84, 85, 86, 87, 144, 62, 63, 64, + /* 1050 */ 65, 4, 146, 68, 69, 70, 71, 72, 73, 74, + /* 1060 */ 75, 177, 77, 78, 79, 80, 81, 82, 83, 84, + /* 1070 */ 85, 86, 87, 163, 190, 159, 191, 192, 193, 195, + /* 1080 */ 196, 97, 97, 62, 63, 64, 65, 97, 104, 68, + /* 1090 */ 69, 70, 71, 72, 73, 74, 75, 104, 77, 78, + /* 1100 */ 79, 80, 81, 82, 83, 84, 85, 86, 87, 185, + /* 1110 */ 172, 173, 13, 62, 63, 64, 65, 159, 97, 68, + /* 1120 */ 69, 70, 71, 72, 73, 74, 75, 177, 77, 78, + /* 1130 */ 79, 80, 81, 82, 83, 84, 85, 86, 87, 97, + /* 1140 */ 190, 185, 104, 219, 260, 261, 104, 13, 97, 62, + /* 1150 */ 63, 64, 65, 106, 107, 68, 69, 70, 71, 72, + /* 1160 */ 73, 74, 75, 116, 77, 78, 79, 80, 81, 82, + /* 1170 */ 83, 84, 85, 86, 87, 219, 97, 138, 139, 62, + /* 1180 */ 63, 64, 65, 104, 97, 68, 69, 70, 71, 72, + /* 1190 */ 73, 74, 75, 243, 77, 78, 79, 80, 81, 82, + /* 1200 */ 83, 84, 85, 86, 87, 185, 55, 4, 197, 25, + /* 1210 */ 96, 62, 63, 64, 65, 98, 205, 68, 69, 70, + /* 1220 */ 71, 72, 73, 74, 75, 30, 77, 78, 79, 80, + /* 1230 */ 81, 82, 83, 84, 85, 86, 87, 4, 89, 219, + /* 1240 */ 89, 4, 62, 63, 64, 65, 144, 13, 68, 69, + /* 1250 */ 70, 71, 72, 73, 74, 75, 97, 77, 78, 79, + /* 1260 */ 80, 81, 82, 83, 84, 85, 86, 87, 4, 89, + /* 1270 */ 97, 97, 104, 89, 62, 63, 64, 65, 104, 15, + /* 1280 */ 68, 69, 70, 71, 72, 73, 74, 75, 191, 77, + /* 1290 */ 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + /* 1300 */ 177, 177, 177, 177, 177, 4, 42, 185, 185, 106, + /* 1310 */ 186, 187, 97, 190, 190, 190, 190, 190, 177, 104, + /* 1320 */ 117, 198, 199, 59, 198, 199, 99, 177, 64, 4, + /* 1330 */ 99, 190, 235, 209, 83, 211, 96, 100, 101, 106, + /* 1340 */ 190, 219, 219, 106, 107, 81, 82, 4, 198, 199, + /* 1350 */ 117, 4, 88, 191, 191, 232, 233, 97, 232, 233, + /* 1360 */ 96, 97, 15, 236, 237, 101, 177, 103, 243, 96, + /* 1370 */ 106, 107, 4, 177, 251, 111, 4, 251, 177, 190, + /* 1380 */ 4, 191, 118, 119, 120, 121, 190, 198, 199, 42, + /* 1390 */ 4, 190, 7, 135, 4, 201, 11, 235, 235, 198, + /* 1400 */ 199, 251, 101, 97, 254, 201, 59, 106, 107, 177, + /* 1410 */ 97, 64, 4, 181, 252, 97, 152, 32, 154, 155, + /* 1420 */ 156, 232, 190, 177, 17, 235, 101, 181, 81, 82, + /* 1430 */ 38, 106, 107, 232, 4, 88, 190, 4, 177, 243, + /* 1440 */ 251, 98, 4, 96, 97, 15, 252, 177, 101, 106, + /* 1450 */ 103, 190, 251, 106, 107, 4, 252, 132, 111, 4, + /* 1460 */ 190, 96, 6, 162, 4, 118, 119, 120, 121, 101, + /* 1470 */ 38, 96, 42, 101, 106, 107, 97, 101, 106, 107, + /* 1480 */ 96, 38, 106, 107, 141, 96, 118, 101, 115, 59, + /* 1490 */ 118, 101, 106, 107, 64, 4, 106, 107, 237, 152, + /* 1500 */ 177, 154, 155, 156, 118, 96, 177, 177, 118, 133, + /* 1510 */ 177, 81, 82, 190, 106, 107, 4, 177, 88, 190, + /* 1520 */ 190, 198, 199, 190, 129, 4, 96, 15, 198, 199, + /* 1530 */ 190, 101, 64, 103, 101, 98, 106, 107, 103, 106, + /* 1540 */ 107, 111, 123, 12, 106, 107, 276, 142, 118, 119, + /* 1550 */ 120, 121, 101, 128, 42, 177, 101, 106, 107, 181, + /* 1560 */ 71, 106, 107, 71, 177, 157, 106, 107, 190, 153, + /* 1570 */ 177, 59, 89, 118, 251, 139, 64, 190, 4, 146, + /* 1580 */ 22, 251, 152, 190, 154, 155, 156, 47, 158, 177, + /* 1590 */ 152, 100, 89, 81, 82, 39, 123, 106, 107, 177, + /* 1600 */ 88, 141, 190, 39, 177, 276, 161, 96, 96, 97, + /* 1610 */ 198, 199, 190, 101, 104, 103, 165, 190, 106, 107, + /* 1620 */ 198, 199, 143, 111, 37, 198, 199, 106, 107, 95, + /* 1630 */ 118, 119, 120, 121, 96, 63, 64, 65, 4, 118, + /* 1640 */ 68, 69, 70, 71, 72, 73, 74, 75, 103, 77, + /* 1650 */ 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + /* 1660 */ 4, 177, 97, 251, 152, 91, 154, 155, 156, 93, + /* 1670 */ 4, 15, 177, 251, 190, 177, 177, 97, 251, 177, + /* 1680 */ 106, 107, 198, 199, 238, 190, 177, 95, 190, 190, + /* 1690 */ 177, 36, 190, 198, 199, 4, 198, 199, 42, 190, + /* 1700 */ 198, 199, 4, 190, 177, 177, 177, 198, 199, 177, + /* 1710 */ 252, 198, 199, 189, 273, 59, 90, 190, 190, 190, + /* 1720 */ 64, 204, 190, 204, 275, 198, 199, 198, 199, 177, + /* 1730 */ 198, 199, 177, 180, 177, 251, 181, 81, 82, 275, + /* 1740 */ 106, 107, 190, 176, 88, 190, 251, 190, 178, 251, + /* 1750 */ 198, 199, 96, 251, 49, 177, 177, 101, 176, 103, + /* 1760 */ 251, 127, 106, 107, 251, 104, 177, 111, 190, 190, + /* 1770 */ 60, 177, 106, 107, 118, 119, 120, 121, 251, 190, + /* 1780 */ 251, 4, 91, 251, 190, 178, 177, 198, 199, 91, + /* 1790 */ 176, 212, 198, 199, 177, 183, 177, 106, 107, 190, + /* 1800 */ 184, 177, 56, 251, 106, 107, 177, 190, 152, 190, + /* 1810 */ 154, 155, 156, 177, 190, 198, 199, 198, 199, 190, + /* 1820 */ 177, 212, 198, 199, 221, 177, 190, 176, 162, 227, + /* 1830 */ 177, 177, 138, 190, 198, 199, 177, 258, 190, 151, + /* 1840 */ 251, 198, 199, 190, 190, 251, 198, 199, 228, 190, + /* 1850 */ 177, 198, 199, 177, 4, 148, 177, 198, 199, 229, + /* 1860 */ 177, 177, 230, 190, 149, 252, 190, 258, 251, 190, + /* 1870 */ 251, 198, 199, 190, 190, 251, 177, 198, 199, 177, + /* 1880 */ 147, 198, 199, 106, 107, 150, 4, 251, 231, 190, + /* 1890 */ 177, 87, 190, 177, 251, 177, 212, 198, 199, 251, + /* 1900 */ 198, 199, 4, 190, 251, 177, 190, 177, 190, 64, + /* 1910 */ 251, 198, 199, 96, 198, 199, 198, 199, 190, 4, + /* 1920 */ 190, 4, 177, 146, 251, 4, 198, 199, 198, 199, + /* 1930 */ 251, 259, 203, 87, 251, 190, 255, 203, 177, 4, + /* 1940 */ 158, 177, 258, 198, 199, 177, 4, 241, 99, 4, + /* 1950 */ 251, 190, 177, 251, 190, 177, 106, 107, 190, 198, + /* 1960 */ 199, 241, 198, 199, 251, 190, 99, 251, 190, 251, + /* 1970 */ 177, 177, 177, 198, 199, 4, 198, 199, 4, 251, + /* 1980 */ 4, 251, 132, 190, 190, 190, 4, 200, 106, 107, + /* 1990 */ 223, 198, 199, 198, 199, 177, 251, 4, 177, 4, + /* 2000 */ 118, 4, 181, 177, 106, 107, 123, 181, 190, 4, + /* 2010 */ 139, 190, 251, 200, 4, 251, 190, 4, 208, 4, + /* 2020 */ 31, 106, 107, 106, 107, 177, 251, 106, 107, 251, + /* 2030 */ 177, 4, 177, 118, 181, 118, 181, 200, 190, 118, + /* 2040 */ 202, 106, 107, 190, 251, 190, 251, 122, 106, 107, + /* 2050 */ 203, 106, 107, 177, 177, 177, 177, 181, 200, 208, + /* 2060 */ 118, 177, 164, 118, 200, 200, 190, 190, 190, 190, + /* 2070 */ 200, 100, 177, 177, 190, 200, 203, 106, 107, 99, + /* 2080 */ 106, 107, 106, 107, 177, 190, 190, 152, 106, 107, + /* 2090 */ 180, 99, 118, 177, 180, 177, 264, 190, 241, 106, + /* 2100 */ 107, 106, 107, 106, 107, 100, 190, 241, 190, 133, + /* 2110 */ 99, 106, 107, 118, 27, 100, 106, 107, 158, 106, + /* 2120 */ 107, 106, 107, 177, 177, 265, 227, 177, 146, 177, + /* 2130 */ 177, 177, 99, 106, 107, 215, 190, 190, 62, 99, + /* 2140 */ 190, 99, 190, 190, 190, 96, 215, 250, 180, 241, + /* 2150 */ 99, 227, 180, 241, 99, 217, 60, 99, 165, 99, + /* 2160 */ 163, 217, 152, 99, 99, 152, 217, 241, 217, 99, + /* 2170 */ 268, 18, 268, 241, 99, 241, 99, 99, 16, 152, + /* 2180 */ 224, 270, 201, 226, 261, 256, 201, 210, 261, 206, + /* 2190 */ 242, 227, 202, 175, 224, 240, 263, 191, 191, 191, + /* 2200 */ 242, 242, 262, 191, 216, 239, 222, 207, 207, 207, + /* 2210 */ 274, 55, 198, 277, 253, 198, 277, 277, 277, 277, + /* 2220 */ 211, +}; +#define YY_SHIFT_USE_DFLT (-81) +#define YY_SHIFT_COUNT (434) +#define YY_SHIFT_MIN (-80) +#define YY_SHIFT_MAX (2162) +static const short yy_shift_ofst[] = { + /* 0 */ 1184, -4, 201, 1151, 819, 1512, 1512, 689, 361, 1430, + /* 10 */ 1656, 1656, 770, 364, 364, 157, 81, 179, 1347, 1264, + /* 20 */ 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, + /* 30 */ 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, + /* 40 */ 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, + /* 50 */ 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 370, 798, + /* 60 */ 181, 370, 2010, 2010, 2010, 2010, 2010, 887, 887, 565, + /* 70 */ 277, 4, 1455, 1451, 1433, 1376, 1390, 1386, 1372, 1368, + /* 80 */ 1325, 448, 1237, 2013, 2013, 2027, 439, 2013, 2015, 2010, + /* 90 */ 565, 1039, 538, 538, 735, 84, 44, 171, 859, 775, + /* 100 */ 906, 773, 910, 636, 1301, 5, 450, 5, 443, 413, + /* 110 */ 185, 2005, 1995, 1997, 1993, 1982, 1976, 1974, 1971, 1945, + /* 120 */ 1666, 1942, 1935, 1921, 1917, 1915, 1898, 1850, 1491, 1882, + /* 130 */ 1047, 1634, 1521, 902, 140, 1343, 1343, 1777, 1460, 1343, + /* 140 */ 1438, 780, 1408, 349, 562, 216, 620, 1698, 1691, 1574, + /* 150 */ 90, 829, 829, 829, 872, 544, 2156, 2156, 2156, 2156, + /* 160 */ 2156, -81, -81, 459, 619, 619, 619, 619, 619, 619, + /* 170 */ 619, 619, 619, 645, 327, 683, 1180, 1149, 1117, 1087, + /* 180 */ 1051, 1021, 985, 958, 916, 767, 1212, 1572, 509, 509, + /* 190 */ -60, -38, -38, -38, -38, -38, 578, -80, 314, 87, + /* 200 */ 87, 41, 155, 75, 58, 58, 58, -6, 186, 862, + /* 210 */ -47, 808, 1385, 1233, 1203, 206, 908, 424, 400, 25, + /* 220 */ 729, 729, 679, 1215, -2, 855, 729, 442, 346, 855, + /* 230 */ 750, 750, 187, -9, 169, 2162, 2078, 2077, 2075, 2153, + /* 240 */ 2153, 2070, 2065, 2096, 2064, 2096, 2060, 2096, 2058, 2096, + /* 250 */ 2055, 1710, 1688, 2051, 1710, 2076, 2049, 2042, 2040, 2076, + /* 260 */ 1688, 2033, 1960, 2087, 2011, 1710, 1992, 1710, 1980, 1817, + /* 270 */ 1883, 1883, 1883, 1883, 1989, 1817, 1883, 1925, 1883, 1989, + /* 280 */ 1883, 1883, 1871, 1867, 1849, 1782, 1817, 1846, 1817, 1845, + /* 290 */ 1804, 1735, 1733, 1715, 1707, 1688, 1694, 1746, 1661, 1710, + /* 300 */ 1705, 1705, 1626, 1626, 1626, 1626, -81, -81, -81, -81, + /* 310 */ -81, -81, -81, -81, -81, -81, 564, 79, 158, 45, + /* 320 */ 702, 243, -49, 398, 1174, 1079, 1042, 984, 788, 2, + /* 330 */ 471, -20, 758, 678, 344, 144, -44, 1655, 1587, 1576, + /* 340 */ 1592, 1580, 1565, 1545, 1538, 1479, 1534, 1511, 1473, 1445, + /* 350 */ 1564, 1510, 1556, 1540, 1558, 1503, 1483, 1436, 1416, 1492, + /* 360 */ 1489, 1425, 1405, 1531, 1419, 1437, 1435, 1468, 1395, 1373, + /* 370 */ 1409, 1443, 1389, 1384, 1379, 1375, 1432, 1456, 1365, 1392, + /* 380 */ 1407, 1318, 1313, 1306, 1168, 1258, 1273, 1260, 1240, 1168, + /* 390 */ 1251, 1231, 1227, 1102, 1173, 1159, 1195, 1234, 1114, 993, + /* 400 */ 1134, 1038, 1099, 993, 990, 937, 846, 895, 870, 848, + /* 410 */ 867, 825, 757, 805, 675, 652, 571, 644, 625, 613, + /* 420 */ 571, 438, 474, 421, 399, 406, 355, 362, 231, 225, + /* 430 */ 166, 143, 63, -37, -61, +}; +#define YY_REDUCE_USE_DFLT (-100) +#define YY_REDUCE_COUNT (315) +#define YY_REDUCE_MIN (-99) +#define YY_REDUCE_MAX (2018) +static const short yy_reduce_ofst[] = { + /* 0 */ 615, 1123, 699, -99, 764, 1126, 189, 705, 1201, 1150, + /* 10 */ 1189, 261, 196, 884, 599, 1124, 1795, 1793, 1778, 1775, + /* 20 */ 1764, 1761, 1745, 1730, 1728, 1718, 1716, 1713, 1702, 1699, + /* 30 */ 1683, 1679, 1673, 1659, 1653, 1648, 1643, 1636, 1624, 1619, + /* 40 */ 1617, 1594, 1589, 1552, 1532, 1529, 1527, 1513, 1509, 1502, + /* 50 */ 1498, 1495, 1484, 1427, 1422, 1412, 1330, 1323, 490, 1127, + /* 60 */ 214, 769, 1684, 1609, 1579, 124, 67, 596, 175, 547, + /* 70 */ 1162, 1261, 1876, 1855, 1853, 1826, 1821, 1555, 1378, 1246, + /* 80 */ 1232, 1329, 203, 1196, 1125, 131, 347, 950, 1270, 195, + /* 90 */ 885, 265, 1204, 1194, 125, 377, 1011, 1954, 1953, 1916, + /* 100 */ 1654, 1654, 1952, 1950, 1947, 697, 1946, 11, 1654, 1918, + /* 110 */ 1916, 1907, 1654, 1654, 1654, 1654, 1654, 1654, 1654, 1896, + /* 120 */ 1654, 1654, 1895, 1654, 1654, 1884, 1654, 1654, 1654, 1879, + /* 130 */ 1878, 1877, 1848, 1818, 1794, 1190, 1163, 1768, 1333, 1097, + /* 140 */ 1676, 1654, 1629, 1578, 1557, 1528, 632, 1499, 1393, 1387, + /* 150 */ 1340, 1141, 666, 489, 938, 1122, 1020, 956, 924, 791, + /* 160 */ 763, 746, 205, 1458, 1458, 1458, 1458, 1458, 1458, 1458, + /* 170 */ 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, + /* 180 */ 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, + /* 190 */ 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 2009, 2017, + /* 200 */ 2014, 1458, 1936, 1936, 2002, 2001, 2000, 1966, 1988, 1961, + /* 210 */ 1984, 1933, 1940, 2012, 2008, 2007, 2006, 1970, 2018, 1964, + /* 220 */ 1959, 1958, 1990, 1977, 1983, 1985, 1948, 1955, 1929, 1981, + /* 230 */ 1927, 1923, 1458, 1957, 1956, 1911, 1857, 1934, 1932, 1904, + /* 240 */ 1902, 1926, 1857, 1951, 1857, 1949, 1857, 1944, 1857, 1938, + /* 250 */ 1912, 1972, 1924, 1908, 1968, 1931, 1897, 1857, 1857, 1920, + /* 260 */ 1899, 1857, 1860, 1832, 1866, 1914, 1857, 1910, 1857, 1873, + /* 270 */ 1875, 1870, 1865, 1864, 1851, 1847, 1858, 1838, 1837, 1810, + /* 280 */ 1813, 1787, 1767, 1720, 1706, 1681, 1734, 1672, 1729, 1613, + /* 290 */ 1458, 1657, 1632, 1630, 1620, 1602, 1603, 1616, 1612, 1553, + /* 300 */ 1607, 1570, 1651, 1614, 1582, 1567, 1464, 1449, 1441, 1519, + /* 310 */ 1517, 1446, 1458, 1458, 1458, 1524, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 729, 1037, 1142, 1142, 1026, 1026, 1026, 1142, 1026, 1026, + /* 10 */ 1026, 1026, 900, 1148, 1148, 1148, 1026, 1026, 1026, 1026, + /* 20 */ 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + /* 30 */ 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + /* 40 */ 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + /* 50 */ 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1148, 894, + /* 60 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 774, + /* 70 */ 890, 900, 1148, 1148, 1148, 1148, 1148, 962, 949, 940, + /* 80 */ 1148, 1148, 1148, 972, 972, 955, 842, 972, 1148, 1148, + /* 90 */ 1148, 1148, 928, 928, 1027, 1148, 766, 1112, 1117, 1013, + /* 100 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 901, 1148, + /* 110 */ 1013, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, + /* 120 */ 1148, 963, 956, 950, 941, 1148, 1148, 1148, 1148, 1148, + /* 130 */ 1148, 1148, 1148, 1148, 1148, 890, 890, 1148, 1148, 890, + /* 140 */ 1148, 1148, 1148, 1014, 1148, 1148, 763, 1148, 1148, 1148, + /* 150 */ 735, 1058, 1148, 1148, 729, 1142, 1142, 1142, 1142, 1142, + /* 160 */ 1142, 1135, 880, 935, 906, 945, 933, 937, 1038, 1031, + /* 170 */ 1032, 1030, 936, 1027, 1027, 1027, 1027, 1027, 1027, 1027, + /* 180 */ 1027, 1027, 1027, 1027, 1027, 1027, 1027, 988, 1000, 987, + /* 190 */ 995, 1004, 1015, 999, 996, 990, 989, 991, 1148, 1148, + /* 200 */ 1148, 992, 1148, 1148, 1148, 1148, 1148, 893, 1148, 1148, + /* 210 */ 864, 1148, 1086, 1148, 1148, 776, 1148, 878, 738, 944, + /* 220 */ 918, 918, 809, 833, 798, 928, 918, 908, 1033, 928, + /* 230 */ 1148, 1148, 993, 891, 878, 1126, 909, 909, 909, 1111, + /* 240 */ 1111, 909, 909, 855, 909, 855, 909, 855, 909, 855, + /* 250 */ 909, 760, 944, 909, 760, 846, 968, 909, 909, 846, + /* 260 */ 944, 909, 1093, 1091, 909, 760, 909, 760, 909, 1046, + /* 270 */ 844, 844, 844, 844, 825, 1046, 844, 809, 844, 825, + /* 280 */ 844, 844, 1148, 909, 909, 1148, 1046, 1052, 1046, 1027, + /* 290 */ 994, 934, 922, 932, 929, 944, 1148, 757, 828, 760, + /* 300 */ 746, 746, 734, 734, 734, 734, 1139, 1139, 1135, 811, + /* 310 */ 811, 896, 1003, 1002, 1001, 785, 1039, 1148, 1148, 1148, + /* 320 */ 1148, 1148, 1148, 1060, 1148, 1148, 1148, 1148, 1148, 1148, + /* 330 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 730, 1148, + /* 340 */ 1148, 1148, 1148, 1148, 1129, 1148, 1148, 1148, 1148, 1148, + /* 350 */ 1148, 1090, 1089, 1148, 1148, 1148, 1148, 1148, 1148, 1148, + /* 360 */ 1148, 1148, 1148, 1078, 1148, 1148, 1148, 1148, 1148, 1148, + /* 370 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, + /* 380 */ 1148, 1148, 1148, 1148, 867, 869, 1148, 1148, 1148, 868, + /* 390 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 930, + /* 400 */ 1148, 923, 1148, 1036, 1148, 1017, 1025, 1148, 1148, 1148, + /* 410 */ 1148, 1148, 1016, 1148, 1148, 1148, 1144, 1148, 1148, 1148, + /* 420 */ 1143, 1148, 1148, 1148, 1148, 1148, 1028, 980, 1148, 979, + /* 430 */ 978, 769, 1148, 744, 1148, 726, 731, 1128, 1125, 1127, + /* 440 */ 1122, 1123, 1121, 1124, 1120, 1118, 1119, 1116, 1114, 1113, + /* 450 */ 1115, 1110, 1106, 1066, 1064, 1062, 1071, 1070, 1069, 1068, + /* 460 */ 1067, 1063, 1061, 1065, 1059, 959, 947, 938, 862, 1105, + /* 470 */ 1103, 1104, 1057, 1055, 1056, 861, 860, 859, 854, 853, + /* 480 */ 852, 851, 1132, 1141, 1140, 1138, 1137, 1136, 1130, 1131, + /* 490 */ 1044, 1043, 1041, 1040, 1042, 762, 1082, 1085, 1084, 1083, + /* 500 */ 1088, 1087, 1080, 1092, 1097, 1096, 1101, 1100, 1099, 1098, + /* 510 */ 1095, 1077, 967, 966, 964, 969, 961, 960, 965, 952, + /* 520 */ 958, 957, 948, 951, 847, 943, 939, 942, 863, 1081, + /* 530 */ 858, 857, 856, 761, 756, 911, 755, 754, 765, 831, + /* 540 */ 832, 840, 843, 838, 841, 837, 836, 835, 839, 834, + /* 550 */ 830, 768, 767, 775, 824, 802, 800, 799, 803, 816, + /* 560 */ 815, 822, 821, 820, 819, 818, 814, 817, 813, 812, + /* 570 */ 804, 797, 796, 810, 795, 827, 826, 823, 794, 850, + /* 580 */ 849, 848, 845, 793, 792, 791, 790, 789, 788, 986, + /* 590 */ 985, 1006, 977, 865, 872, 871, 870, 874, 875, 885, + /* 600 */ 883, 882, 881, 917, 916, 915, 914, 913, 912, 905, + /* 610 */ 903, 899, 898, 904, 902, 920, 921, 919, 897, 889, + /* 620 */ 887, 888, 886, 974, 971, 973, 970, 907, 895, 892, + /* 630 */ 879, 925, 924, 1029, 1018, 1008, 1019, 910, 1007, 1005, + /* 640 */ 1028, 1025, 1020, 1102, 1024, 1012, 1011, 1010, 1147, 1145, + /* 650 */ 1146, 1049, 1051, 1054, 1053, 1050, 927, 926, 1048, 1047, + /* 660 */ 1009, 984, 781, 779, 780, 1074, 1073, 1076, 1075, 1072, + /* 670 */ 783, 782, 778, 777, 998, 997, 982, 1021, 1022, 981, + /* 680 */ 1023, 983, 770, 873, 866, 976, 975, 808, 807, 806, + /* 690 */ 805, 877, 876, 787, 801, 786, 784, 764, 759, 758, + /* 700 */ 753, 751, 748, 750, 747, 752, 749, 745, 743, 742, + /* 710 */ 741, 740, 739, 773, 772, 771, 769, 737, 736, 733, + /* 720 */ 732, 728, 727, 725, +}; + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* ILLEGAL => nothing */ + 0, /* COMMENT => nothing */ + 0, /* SPACE => nothing */ + 0, /* ID => nothing */ + 4, /* ABORT => ID */ + 4, /* ACTION => ID */ + 4, /* AFTER => ID */ + 4, /* ANALYZE => ID */ + 4, /* ASC => ID */ + 4, /* ATTACH => ID */ + 4, /* BEFORE => ID */ + 4, /* BEGIN => ID */ + 4, /* BY => ID */ + 4, /* CASCADE => ID */ + 4, /* CAST => ID */ + 4, /* COLUMNKW => ID */ + 4, /* CONFLICT => ID */ + 4, /* DATABASE => ID */ + 4, /* DEFERRED => ID */ + 4, /* DESC => ID */ + 4, /* DETACH => ID */ + 4, /* EACH => ID */ + 4, /* END => ID */ + 4, /* EXCLUSIVE => ID */ + 4, /* EXPLAIN => ID */ + 4, /* FAIL => ID */ + 4, /* FOR => ID */ + 4, /* IGNORE => ID */ + 4, /* IMMEDIATE => ID */ + 4, /* INDEXED => ID */ + 4, /* INITIALLY => ID */ + 4, /* INSTEAD => ID */ + 4, /* LIKE_KW => ID */ + 4, /* MATCH => ID */ + 4, /* NO => ID */ + 4, /* PLAN => ID */ + 4, /* QUERY => ID */ + 4, /* KEY => ID */ + 4, /* OF => ID */ + 4, /* OFFSET => ID */ + 4, /* PRAGMA => ID */ + 4, /* RAISE => ID */ + 4, /* RECURSIVE => ID */ + 4, /* RELEASE => ID */ + 4, /* REPLACE => ID */ + 4, /* RESTRICT => ID */ + 4, /* ROW => ID */ + 4, /* ROLLBACK => ID */ + 4, /* SAVEPOINT => ID */ + 4, /* TEMP => ID */ + 4, /* TRIGGER => ID */ + 4, /* VACUUM => ID */ + 4, /* VIEW => ID */ + 4, /* VIRTUAL => ID */ + 4, /* WITH => ID */ + 4, /* WITHOUT => ID */ + 4, /* REINDEX => ID */ + 4, /* RENAME => ID */ + 4, /* CTIME_KW => ID */ + 4, /* IF => ID */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ + QList* tokens = nullptr; +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyidxMax; /* Maximum value of yyidx */ +#endif + int yyerrcnt; /* Shifts left before out of the error */ + sqlite3_parseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +void *sqlite3_parseCopyParserState(void* other) +{ + yyParser *pParser; + yyParser *otherParser = (yyParser*)other; + + // Copy parser + pParser = (yyParser*)malloc((size_t)sizeof(yyParser)); + memcpy(pParser, other, (size_t)sizeof(yyParser)); + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = malloc((size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)other)->yystack, (size_t)stackSize); +#endif + + for (int i = 0; i <= pParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList(); + *(pParser->yystack[i].tokens) = *(otherParser->yystack[i].tokens); + } + + return pParser; +} + +void sqlite3_parseAddToken(void* other, Token* token) +{ + yyParser *otherParser = (yyParser*)other; + if (otherParser->yyidx < 0) + return; // Nothing on stack yet. Might happen when parsing just whitespaces, nothing else. + + otherParser->yystack[otherParser->yyidx].tokens->append(token); +} + +void sqlite3_parseRestoreParserState(void* saved, void* target) +{ + yyParser *pParser = (yyParser*)target; + yyParser *savedParser = (yyParser*)saved; + + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + + memcpy(pParser, saved, (size_t)sizeof(yyParser)); + + for (int i = 0; i <= savedParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList(); + *(pParser->yystack[i].tokens) = *(savedParser->yystack[i].tokens); + } + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = relloc(pParser->yystack, (size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)saved)->yystack, (size_t)stackSize); +#endif +} + +void sqlite3_parseFreeSavedState(void* other) +{ + yyParser *pParser = (yyParser*)other; + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + free(other); +} + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
    +**
  • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
  • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
+** +** Outputs: +** None. +*/ +void sqlite3_parseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + "$", "ILLEGAL", "COMMENT", "SPACE", + "ID", "ABORT", "ACTION", "AFTER", + "ANALYZE", "ASC", "ATTACH", "BEFORE", + "BEGIN", "BY", "CASCADE", "CAST", + "COLUMNKW", "CONFLICT", "DATABASE", "DEFERRED", + "DESC", "DETACH", "EACH", "END", + "EXCLUSIVE", "EXPLAIN", "FAIL", "FOR", + "IGNORE", "IMMEDIATE", "INDEXED", "INITIALLY", + "INSTEAD", "LIKE_KW", "MATCH", "NO", + "PLAN", "QUERY", "KEY", "OF", + "OFFSET", "PRAGMA", "RAISE", "RECURSIVE", + "RELEASE", "REPLACE", "RESTRICT", "ROW", + "ROLLBACK", "SAVEPOINT", "TEMP", "TRIGGER", + "VACUUM", "VIEW", "VIRTUAL", "WITH", + "WITHOUT", "REINDEX", "RENAME", "CTIME_KW", + "IF", "ANY", "OR", "AND", + "NOT", "IS", "BETWEEN", "IN", + "ISNULL", "NOTNULL", "NE", "EQ", + "GT", "LE", "LT", "GE", + "ESCAPE", "BITAND", "BITOR", "LSHIFT", + "RSHIFT", "PLUS", "MINUS", "STAR", + "SLASH", "REM", "CONCAT", "COLLATE", + "BITNOT", "SEMI", "TRANSACTION", "ID_TRANS", + "COMMIT", "TO", "CREATE", "TABLE", + "LP", "RP", "AS", "DOT", + "ID_TAB_NEW", "ID_DB", "CTX_ROWID_KW", "EXISTS", + "COMMA", "ID_COL_NEW", "STRING", "JOIN_KW", + "ID_COL_TYPE", "CONSTRAINT", "DEFAULT", "NULL", + "PRIMARY", "UNIQUE", "CHECK", "REFERENCES", + "ID_CONSTR", "ID_COLLATE", "ID_TAB", "INTEGER", + "FLOAT", "BLOB", "AUTOINCR", "ON", + "INSERT", "DELETE", "UPDATE", "ID_FK_MATCH", + "SET", "DEFERRABLE", "FOREIGN", "DROP", + "ID_VIEW_NEW", "ID_VIEW", "UNION", "ALL", + "EXCEPT", "INTERSECT", "SELECT", "VALUES", + "DISTINCT", "ID_ALIAS", "FROM", "USING", + "JOIN", "ID_JOIN_OPTS", "ID_IDX", "ORDER", + "GROUP", "HAVING", "LIMIT", "WHERE", + "ID_COL", "INTO", "VARIABLE", "CASE", + "ID_FN", "ID_ERR_MSG", "WHEN", "THEN", + "ELSE", "INDEX", "ID_IDX_NEW", "ID_PRAGMA", + "ID_TRIG_NEW", "ID_TRIG", "ALTER", "ADD", + "error", "cmd", "input", "cmdlist", + "ecmd", "explain", "cmdx", "transtype", + "trans_opt", "nm", "savepoint_opt", "temp", + "ifnotexists", "fullname", "columnlist", "conslist_opt", + "table_options", "select", "column", "columnid", + "type", "carglist", "id", "ids", + "typetoken", "typename", "signed", "plus_num", + "minus_num", "ccons", "term", "expr", + "onconf", "sortorder", "autoinc", "idxlist_opt", + "refargs", "defer_subclause", "refarg", "refact", + "init_deferred_pred_opt", "conslist", "tconscomma", "tcons", + "idxlist", "defer_subclause_opt", "resolvetype", "orconf", + "raisetype", "ifexists", "select_stmt", "with", + "selectnowith", "oneselect", "multiselect_op", "values", + "distinct", "selcollist", "from", "where_opt", + "groupby_opt", "having_opt", "orderby_opt", "limit_opt", + "nexprlist", "exprlist", "sclp", "as", + "joinsrc", "singlesrc", "seltablist", "joinop", + "joinconstr_opt", "dbnm", "indexed_opt", "inscollist", + "sortlist", "delete_stmt", "update_stmt", "setlist", + "insert_stmt", "insert_cmd", "inscollist_opt", "exprx", + "not_opt", "likeop", "case_operand", "case_exprlist", + "case_else", "uniqueflag", "idxlist_single", "collate", + "nmnum", "number", "trigger_time", "trigger_event", + "foreach_clause", "when_clause", "trigger_cmd_list", "trigger_cmd", + "database_kw_opt", "key_opt", "kwcolumn_opt", "create_vtab", + "vtabarglist", "vtabarg", "vtabargtoken", "anylist", + "wqlist", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "input ::= cmdlist", + /* 1 */ "cmdlist ::= cmdlist ecmd", + /* 2 */ "cmdlist ::= ecmd", + /* 3 */ "ecmd ::= SEMI", + /* 4 */ "ecmd ::= explain cmdx SEMI", + /* 5 */ "explain ::=", + /* 6 */ "explain ::= EXPLAIN", + /* 7 */ "explain ::= EXPLAIN QUERY PLAN", + /* 8 */ "cmdx ::= cmd", + /* 9 */ "cmd ::= BEGIN transtype trans_opt", + /* 10 */ "trans_opt ::=", + /* 11 */ "trans_opt ::= TRANSACTION", + /* 12 */ "trans_opt ::= TRANSACTION nm", + /* 13 */ "trans_opt ::= TRANSACTION ID_TRANS", + /* 14 */ "transtype ::=", + /* 15 */ "transtype ::= DEFERRED", + /* 16 */ "transtype ::= IMMEDIATE", + /* 17 */ "transtype ::= EXCLUSIVE", + /* 18 */ "cmd ::= COMMIT trans_opt", + /* 19 */ "cmd ::= END trans_opt", + /* 20 */ "cmd ::= ROLLBACK trans_opt", + /* 21 */ "savepoint_opt ::= SAVEPOINT", + /* 22 */ "savepoint_opt ::=", + /* 23 */ "cmd ::= SAVEPOINT nm", + /* 24 */ "cmd ::= RELEASE savepoint_opt nm", + /* 25 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt nm", + /* 26 */ "cmd ::= SAVEPOINT ID_TRANS", + /* 27 */ "cmd ::= RELEASE savepoint_opt ID_TRANS", + /* 28 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt ID_TRANS", + /* 29 */ "cmd ::= CREATE temp TABLE ifnotexists fullname LP columnlist conslist_opt RP table_options", + /* 30 */ "cmd ::= CREATE temp TABLE ifnotexists fullname AS select", + /* 31 */ "cmd ::= CREATE temp TABLE ifnotexists nm DOT ID_TAB_NEW", + /* 32 */ "cmd ::= CREATE temp TABLE ifnotexists ID_DB|ID_TAB_NEW", + /* 33 */ "table_options ::=", + /* 34 */ "table_options ::= WITHOUT nm", + /* 35 */ "table_options ::= WITHOUT CTX_ROWID_KW", + /* 36 */ "ifnotexists ::=", + /* 37 */ "ifnotexists ::= IF NOT EXISTS", + /* 38 */ "temp ::= TEMP", + /* 39 */ "temp ::=", + /* 40 */ "columnlist ::= columnlist COMMA column", + /* 41 */ "columnlist ::= column", + /* 42 */ "column ::= columnid type carglist", + /* 43 */ "columnid ::= nm", + /* 44 */ "columnid ::= ID_COL_NEW", + /* 45 */ "id ::= ID", + /* 46 */ "ids ::= ID|STRING", + /* 47 */ "nm ::= id", + /* 48 */ "nm ::= STRING", + /* 49 */ "nm ::= JOIN_KW", + /* 50 */ "type ::=", + /* 51 */ "type ::= typetoken", + /* 52 */ "typetoken ::= typename", + /* 53 */ "typetoken ::= typename LP signed RP", + /* 54 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 55 */ "typename ::= ids", + /* 56 */ "typename ::= typename ids", + /* 57 */ "typename ::= ID_COL_TYPE", + /* 58 */ "signed ::= plus_num", + /* 59 */ "signed ::= minus_num", + /* 60 */ "carglist ::= carglist ccons", + /* 61 */ "carglist ::=", + /* 62 */ "ccons ::= CONSTRAINT nm", + /* 63 */ "ccons ::= DEFAULT term", + /* 64 */ "ccons ::= DEFAULT LP expr RP", + /* 65 */ "ccons ::= DEFAULT PLUS term", + /* 66 */ "ccons ::= DEFAULT MINUS term", + /* 67 */ "ccons ::= DEFAULT id", + /* 68 */ "ccons ::= DEFAULT CTIME_KW", + /* 69 */ "ccons ::= NULL onconf", + /* 70 */ "ccons ::= NOT NULL onconf", + /* 71 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", + /* 72 */ "ccons ::= UNIQUE onconf", + /* 73 */ "ccons ::= CHECK LP expr RP", + /* 74 */ "ccons ::= REFERENCES nm idxlist_opt refargs", + /* 75 */ "ccons ::= defer_subclause", + /* 76 */ "ccons ::= COLLATE ids", + /* 77 */ "ccons ::= CONSTRAINT ID_CONSTR", + /* 78 */ "ccons ::= COLLATE ID_COLLATE", + /* 79 */ "ccons ::= REFERENCES ID_TAB", + /* 80 */ "ccons ::= CHECK LP RP", + /* 81 */ "term ::= NULL", + /* 82 */ "term ::= INTEGER", + /* 83 */ "term ::= FLOAT", + /* 84 */ "term ::= STRING|BLOB", + /* 85 */ "autoinc ::=", + /* 86 */ "autoinc ::= AUTOINCR", + /* 87 */ "refargs ::=", + /* 88 */ "refargs ::= refargs refarg", + /* 89 */ "refarg ::= MATCH nm", + /* 90 */ "refarg ::= ON INSERT refact", + /* 91 */ "refarg ::= ON DELETE refact", + /* 92 */ "refarg ::= ON UPDATE refact", + /* 93 */ "refarg ::= MATCH ID_FK_MATCH", + /* 94 */ "refact ::= SET NULL", + /* 95 */ "refact ::= SET DEFAULT", + /* 96 */ "refact ::= CASCADE", + /* 97 */ "refact ::= RESTRICT", + /* 98 */ "refact ::= NO ACTION", + /* 99 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 100 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 101 */ "init_deferred_pred_opt ::=", + /* 102 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 103 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 104 */ "conslist_opt ::=", + /* 105 */ "conslist_opt ::= COMMA conslist", + /* 106 */ "conslist ::= conslist tconscomma tcons", + /* 107 */ "conslist ::= tcons", + /* 108 */ "tconscomma ::= COMMA", + /* 109 */ "tconscomma ::=", + /* 110 */ "tcons ::= CONSTRAINT nm", + /* 111 */ "tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf", + /* 112 */ "tcons ::= UNIQUE LP idxlist RP onconf", + /* 113 */ "tcons ::= CHECK LP expr RP onconf", + /* 114 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt", + /* 115 */ "tcons ::= CONSTRAINT ID_CONSTR", + /* 116 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES ID_TAB", + /* 117 */ "tcons ::= CHECK LP RP onconf", + /* 118 */ "defer_subclause_opt ::=", + /* 119 */ "defer_subclause_opt ::= defer_subclause", + /* 120 */ "onconf ::=", + /* 121 */ "onconf ::= ON CONFLICT resolvetype", + /* 122 */ "orconf ::=", + /* 123 */ "orconf ::= OR resolvetype", + /* 124 */ "resolvetype ::= raisetype", + /* 125 */ "resolvetype ::= IGNORE", + /* 126 */ "resolvetype ::= REPLACE", + /* 127 */ "cmd ::= DROP TABLE ifexists fullname", + /* 128 */ "cmd ::= DROP TABLE ifexists nm DOT ID_TAB", + /* 129 */ "cmd ::= DROP TABLE ifexists ID_DB|ID_TAB", + /* 130 */ "ifexists ::= IF EXISTS", + /* 131 */ "ifexists ::=", + /* 132 */ "cmd ::= CREATE temp VIEW ifnotexists fullname AS select", + /* 133 */ "cmd ::= CREATE temp VIEW ifnotexists nm DOT ID_VIEW_NEW", + /* 134 */ "cmd ::= CREATE temp VIEW ifnotexists ID_DB|ID_VIEW_NEW", + /* 135 */ "cmd ::= DROP VIEW ifexists fullname", + /* 136 */ "cmd ::= DROP VIEW ifexists nm DOT ID_VIEW", + /* 137 */ "cmd ::= DROP VIEW ifexists ID_DB|ID_VIEW", + /* 138 */ "cmd ::= select_stmt", + /* 139 */ "select_stmt ::= select", + /* 140 */ "select ::= with selectnowith", + /* 141 */ "selectnowith ::= oneselect", + /* 142 */ "selectnowith ::= selectnowith multiselect_op oneselect", + /* 143 */ "selectnowith ::= values", + /* 144 */ "selectnowith ::= selectnowith COMMA values", + /* 145 */ "multiselect_op ::= UNION", + /* 146 */ "multiselect_op ::= UNION ALL", + /* 147 */ "multiselect_op ::= EXCEPT", + /* 148 */ "multiselect_op ::= INTERSECT", + /* 149 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 150 */ "values ::= VALUES LP nexprlist RP", + /* 151 */ "values ::= values COMMA LP exprlist RP", + /* 152 */ "distinct ::= DISTINCT", + /* 153 */ "distinct ::= ALL", + /* 154 */ "distinct ::=", + /* 155 */ "sclp ::= selcollist COMMA", + /* 156 */ "sclp ::=", + /* 157 */ "selcollist ::= sclp expr as", + /* 158 */ "selcollist ::= sclp STAR", + /* 159 */ "selcollist ::= sclp nm DOT STAR", + /* 160 */ "selcollist ::= sclp", + /* 161 */ "selcollist ::= sclp ID_TAB DOT STAR", + /* 162 */ "as ::= AS nm", + /* 163 */ "as ::= ids", + /* 164 */ "as ::= AS ID_ALIAS", + /* 165 */ "as ::= ID_ALIAS", + /* 166 */ "as ::=", + /* 167 */ "from ::=", + /* 168 */ "from ::= FROM joinsrc", + /* 169 */ "joinsrc ::= singlesrc seltablist", + /* 170 */ "joinsrc ::=", + /* 171 */ "seltablist ::= seltablist joinop singlesrc joinconstr_opt", + /* 172 */ "seltablist ::=", + /* 173 */ "singlesrc ::= nm dbnm as indexed_opt", + /* 174 */ "singlesrc ::= LP select RP as", + /* 175 */ "singlesrc ::= LP joinsrc RP as", + /* 176 */ "singlesrc ::=", + /* 177 */ "singlesrc ::= nm DOT", + /* 178 */ "singlesrc ::= nm DOT ID_TAB", + /* 179 */ "singlesrc ::= ID_DB|ID_TAB", + /* 180 */ "singlesrc ::= nm DOT ID_VIEW", + /* 181 */ "singlesrc ::= ID_DB|ID_VIEW", + /* 182 */ "joinconstr_opt ::= ON expr", + /* 183 */ "joinconstr_opt ::= USING LP inscollist RP", + /* 184 */ "joinconstr_opt ::=", + /* 185 */ "dbnm ::=", + /* 186 */ "dbnm ::= DOT nm", + /* 187 */ "fullname ::= nm dbnm", + /* 188 */ "joinop ::= COMMA", + /* 189 */ "joinop ::= JOIN", + /* 190 */ "joinop ::= JOIN_KW JOIN", + /* 191 */ "joinop ::= JOIN_KW nm JOIN", + /* 192 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 193 */ "joinop ::= ID_JOIN_OPTS", + /* 194 */ "indexed_opt ::=", + /* 195 */ "indexed_opt ::= INDEXED BY nm", + /* 196 */ "indexed_opt ::= NOT INDEXED", + /* 197 */ "indexed_opt ::= INDEXED BY ID_IDX", + /* 198 */ "orderby_opt ::=", + /* 199 */ "orderby_opt ::= ORDER BY sortlist", + /* 200 */ "sortlist ::= sortlist COMMA expr sortorder", + /* 201 */ "sortlist ::= expr sortorder", + /* 202 */ "sortorder ::= ASC", + /* 203 */ "sortorder ::= DESC", + /* 204 */ "sortorder ::=", + /* 205 */ "groupby_opt ::=", + /* 206 */ "groupby_opt ::= GROUP BY nexprlist", + /* 207 */ "groupby_opt ::= GROUP BY", + /* 208 */ "having_opt ::=", + /* 209 */ "having_opt ::= HAVING expr", + /* 210 */ "limit_opt ::=", + /* 211 */ "limit_opt ::= LIMIT expr", + /* 212 */ "limit_opt ::= LIMIT expr OFFSET expr", + /* 213 */ "limit_opt ::= LIMIT expr COMMA expr", + /* 214 */ "cmd ::= delete_stmt", + /* 215 */ "delete_stmt ::= with DELETE FROM fullname indexed_opt where_opt", + /* 216 */ "delete_stmt ::= with DELETE FROM", + /* 217 */ "delete_stmt ::= with DELETE FROM nm DOT", + /* 218 */ "delete_stmt ::= with DELETE FROM nm DOT ID_TAB", + /* 219 */ "delete_stmt ::= with DELETE FROM ID_DB|ID_TAB", + /* 220 */ "where_opt ::=", + /* 221 */ "where_opt ::= WHERE expr", + /* 222 */ "where_opt ::= WHERE", + /* 223 */ "cmd ::= update_stmt", + /* 224 */ "update_stmt ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt", + /* 225 */ "update_stmt ::= with UPDATE orconf", + /* 226 */ "update_stmt ::= with UPDATE orconf nm DOT", + /* 227 */ "update_stmt ::= with UPDATE orconf nm DOT ID_TAB", + /* 228 */ "update_stmt ::= with UPDATE orconf ID_DB|ID_TAB", + /* 229 */ "setlist ::= setlist COMMA nm EQ expr", + /* 230 */ "setlist ::= nm EQ expr", + /* 231 */ "setlist ::=", + /* 232 */ "setlist ::= setlist COMMA", + /* 233 */ "setlist ::= setlist COMMA ID_COL", + /* 234 */ "setlist ::= ID_COL", + /* 235 */ "cmd ::= insert_stmt", + /* 236 */ "insert_stmt ::= with insert_cmd INTO fullname inscollist_opt select", + /* 237 */ "insert_stmt ::= with insert_cmd INTO fullname inscollist_opt DEFAULT VALUES", + /* 238 */ "insert_stmt ::= with insert_cmd INTO", + /* 239 */ "insert_stmt ::= with insert_cmd INTO nm DOT", + /* 240 */ "insert_stmt ::= with insert_cmd INTO ID_DB|ID_TAB", + /* 241 */ "insert_stmt ::= with insert_cmd INTO nm DOT ID_TAB", + /* 242 */ "insert_cmd ::= INSERT orconf", + /* 243 */ "insert_cmd ::= REPLACE", + /* 244 */ "inscollist_opt ::=", + /* 245 */ "inscollist_opt ::= LP inscollist RP", + /* 246 */ "inscollist ::= inscollist COMMA nm", + /* 247 */ "inscollist ::= nm", + /* 248 */ "inscollist ::=", + /* 249 */ "inscollist ::= inscollist COMMA ID_COL", + /* 250 */ "inscollist ::= ID_COL", + /* 251 */ "exprx ::= term", + /* 252 */ "exprx ::= CTIME_KW", + /* 253 */ "exprx ::= LP expr RP", + /* 254 */ "exprx ::= id", + /* 255 */ "exprx ::= JOIN_KW", + /* 256 */ "exprx ::= nm DOT nm", + /* 257 */ "exprx ::= nm DOT nm DOT nm", + /* 258 */ "exprx ::= VARIABLE", + /* 259 */ "exprx ::= expr COLLATE ids", + /* 260 */ "exprx ::= CAST LP expr AS typetoken RP", + /* 261 */ "exprx ::= ID LP distinct exprlist RP", + /* 262 */ "exprx ::= ID LP STAR RP", + /* 263 */ "exprx ::= expr AND expr", + /* 264 */ "exprx ::= expr OR expr", + /* 265 */ "exprx ::= expr LT|GT|GE|LE expr", + /* 266 */ "exprx ::= expr EQ|NE expr", + /* 267 */ "exprx ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 268 */ "exprx ::= expr PLUS|MINUS expr", + /* 269 */ "exprx ::= expr STAR|SLASH|REM expr", + /* 270 */ "exprx ::= expr CONCAT expr", + /* 271 */ "exprx ::= expr not_opt likeop expr", + /* 272 */ "exprx ::= expr not_opt likeop expr ESCAPE expr", + /* 273 */ "exprx ::= expr ISNULL|NOTNULL", + /* 274 */ "exprx ::= expr NOT NULL", + /* 275 */ "exprx ::= expr IS not_opt expr", + /* 276 */ "exprx ::= NOT expr", + /* 277 */ "exprx ::= BITNOT expr", + /* 278 */ "exprx ::= MINUS expr", + /* 279 */ "exprx ::= PLUS expr", + /* 280 */ "exprx ::= expr not_opt BETWEEN expr AND expr", + /* 281 */ "exprx ::= expr not_opt IN LP exprlist RP", + /* 282 */ "exprx ::= LP select RP", + /* 283 */ "exprx ::= expr not_opt IN LP select RP", + /* 284 */ "exprx ::= expr not_opt IN nm dbnm", + /* 285 */ "exprx ::= EXISTS LP select RP", + /* 286 */ "exprx ::= CASE case_operand case_exprlist case_else END", + /* 287 */ "exprx ::= RAISE LP IGNORE RP", + /* 288 */ "exprx ::= RAISE LP raisetype COMMA nm RP", + /* 289 */ "exprx ::= nm DOT", + /* 290 */ "exprx ::= nm DOT nm DOT", + /* 291 */ "exprx ::= expr not_opt BETWEEN expr", + /* 292 */ "exprx ::= CASE case_operand case_exprlist case_else", + /* 293 */ "exprx ::= expr not_opt IN LP exprlist", + /* 294 */ "exprx ::= expr not_opt IN ID_DB", + /* 295 */ "exprx ::= expr not_opt IN nm DOT ID_TAB", + /* 296 */ "exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN", + /* 297 */ "exprx ::= nm DOT ID_TAB|ID_COL", + /* 298 */ "exprx ::= nm DOT nm DOT ID_COL", + /* 299 */ "exprx ::= expr COLLATE ID_COLLATE", + /* 300 */ "exprx ::= RAISE LP raisetype COMMA ID_ERR_MSG RP", + /* 301 */ "expr ::= exprx", + /* 302 */ "expr ::=", + /* 303 */ "not_opt ::=", + /* 304 */ "not_opt ::= NOT", + /* 305 */ "likeop ::= LIKE_KW|MATCH", + /* 306 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 307 */ "case_exprlist ::= WHEN expr THEN expr", + /* 308 */ "case_else ::= ELSE expr", + /* 309 */ "case_else ::=", + /* 310 */ "case_operand ::= exprx", + /* 311 */ "case_operand ::=", + /* 312 */ "exprlist ::= nexprlist", + /* 313 */ "exprlist ::=", + /* 314 */ "nexprlist ::= nexprlist COMMA expr", + /* 315 */ "nexprlist ::= exprx", + /* 316 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP where_opt", + /* 317 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON ID_TAB", + /* 318 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists nm DOT ID_IDX_NEW", + /* 319 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists ID_DB|ID_IDX_NEW", + /* 320 */ "uniqueflag ::= UNIQUE", + /* 321 */ "uniqueflag ::=", + /* 322 */ "idxlist_opt ::=", + /* 323 */ "idxlist_opt ::= LP idxlist RP", + /* 324 */ "idxlist ::= idxlist COMMA idxlist_single", + /* 325 */ "idxlist ::= idxlist_single", + /* 326 */ "idxlist_single ::= nm collate sortorder", + /* 327 */ "idxlist_single ::= ID_COL", + /* 328 */ "collate ::=", + /* 329 */ "collate ::= COLLATE ids", + /* 330 */ "collate ::= COLLATE ID_COLLATE", + /* 331 */ "cmd ::= DROP INDEX ifexists fullname", + /* 332 */ "cmd ::= DROP INDEX ifexists nm DOT ID_IDX", + /* 333 */ "cmd ::= DROP INDEX ifexists ID_DB|ID_IDX", + /* 334 */ "cmd ::= VACUUM", + /* 335 */ "cmd ::= VACUUM nm", + /* 336 */ "cmd ::= PRAGMA nm dbnm", + /* 337 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 338 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 339 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 340 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 341 */ "cmd ::= PRAGMA nm DOT ID_PRAGMA", + /* 342 */ "cmd ::= PRAGMA ID_DB|ID_PRAGMA", + /* 343 */ "nmnum ::= plus_num", + /* 344 */ "nmnum ::= nm", + /* 345 */ "nmnum ::= ON", + /* 346 */ "nmnum ::= DELETE", + /* 347 */ "nmnum ::= DEFAULT", + /* 348 */ "plus_num ::= PLUS number", + /* 349 */ "plus_num ::= number", + /* 350 */ "minus_num ::= MINUS number", + /* 351 */ "number ::= INTEGER", + /* 352 */ "number ::= FLOAT", + /* 353 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause BEGIN trigger_cmd_list END", + /* 354 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause", + /* 355 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause BEGIN trigger_cmd_list", + /* 356 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON ID_TAB", + /* 357 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm DOT ID_TRIG_NEW", + /* 358 */ "cmd ::= CREATE temp TRIGGER ifnotexists ID_DB|ID_TRIG_NEW", + /* 359 */ "trigger_time ::= BEFORE", + /* 360 */ "trigger_time ::= AFTER", + /* 361 */ "trigger_time ::= INSTEAD OF", + /* 362 */ "trigger_time ::=", + /* 363 */ "trigger_event ::= DELETE", + /* 364 */ "trigger_event ::= INSERT", + /* 365 */ "trigger_event ::= UPDATE", + /* 366 */ "trigger_event ::= UPDATE OF inscollist", + /* 367 */ "foreach_clause ::=", + /* 368 */ "foreach_clause ::= FOR EACH ROW", + /* 369 */ "when_clause ::=", + /* 370 */ "when_clause ::= WHEN expr", + /* 371 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 372 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 373 */ "trigger_cmd_list ::= SEMI", + /* 374 */ "trigger_cmd ::= update_stmt", + /* 375 */ "trigger_cmd ::= insert_stmt", + /* 376 */ "trigger_cmd ::= delete_stmt", + /* 377 */ "trigger_cmd ::= select_stmt", + /* 378 */ "raisetype ::= ROLLBACK|ABORT|FAIL", + /* 379 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 380 */ "cmd ::= DROP TRIGGER ifexists nm DOT ID_TRIG", + /* 381 */ "cmd ::= DROP TRIGGER ifexists ID_DB|ID_TRIG", + /* 382 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 383 */ "cmd ::= DETACH database_kw_opt expr", + /* 384 */ "key_opt ::=", + /* 385 */ "key_opt ::= KEY expr", + /* 386 */ "database_kw_opt ::= DATABASE", + /* 387 */ "database_kw_opt ::=", + /* 388 */ "cmd ::= REINDEX", + /* 389 */ "cmd ::= REINDEX nm dbnm", + /* 390 */ "cmd ::= REINDEX ID_COLLATE", + /* 391 */ "cmd ::= REINDEX nm DOT ID_TAB|ID_IDX", + /* 392 */ "cmd ::= REINDEX ID_DB|ID_IDX|ID_TAB", + /* 393 */ "cmd ::= ANALYZE", + /* 394 */ "cmd ::= ANALYZE nm dbnm", + /* 395 */ "cmd ::= ANALYZE nm DOT ID_TAB|ID_IDX", + /* 396 */ "cmd ::= ANALYZE ID_DB|ID_IDX|ID_TAB", + /* 397 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 398 */ "cmd ::= ALTER TABLE fullname ADD kwcolumn_opt column", + /* 399 */ "cmd ::= ALTER TABLE fullname RENAME TO ID_TAB_NEW", + /* 400 */ "cmd ::= ALTER TABLE nm DOT ID_TAB", + /* 401 */ "cmd ::= ALTER TABLE ID_DB|ID_TAB", + /* 402 */ "kwcolumn_opt ::=", + /* 403 */ "kwcolumn_opt ::= COLUMNKW", + /* 404 */ "cmd ::= create_vtab", + /* 405 */ "create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 406 */ "create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm dbnm USING nm LP vtabarglist RP", + /* 407 */ "create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm DOT ID_TAB_NEW", + /* 408 */ "create_vtab ::= CREATE VIRTUAL TABLE ifnotexists ID_DB|ID_TAB_NEW", + /* 409 */ "vtabarglist ::= vtabarg", + /* 410 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 411 */ "vtabarg ::=", + /* 412 */ "vtabarg ::= vtabarg vtabargtoken", + /* 413 */ "vtabargtoken ::= ANY", + /* 414 */ "vtabargtoken ::= LP anylist RP", + /* 415 */ "anylist ::=", + /* 416 */ "anylist ::= anylist LP anylist RP", + /* 417 */ "anylist ::= anylist ANY", + /* 418 */ "with ::=", + /* 419 */ "with ::= WITH wqlist", + /* 420 */ "with ::= WITH RECURSIVE wqlist", + /* 421 */ "wqlist ::= nm idxlist_opt AS LP select RP", + /* 422 */ "wqlist ::= wqlist COMMA nm idxlist_opt AS LP select RP", + /* 423 */ "wqlist ::= ID_TAB_NEW", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. +*/ +static void yyGrowStack(yyParser *p){ + int newSize; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + if( pNew ){ + p->yystack = pNew; + p->yystksz = newSize; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", + yyTracePrompt, p->yystksz); + } +#endif + } +} +#endif + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to sqlite3_parse and sqlite3_parseFree. +*/ +void *sqlite3_parseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyidxMax = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yystack = NULL; + pParser->yystksz = 0; + yyGrowStack(pParser); +#endif + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + sqlite3_parseARG_FETCH; + if (parserContext->executeRules) + { + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + case 169: /* cmd */ + case 172: /* ecmd */ + case 174: /* cmdx */ + case 218: /* select_stmt */ + case 245: /* delete_stmt */ + case 246: /* update_stmt */ + case 248: /* insert_stmt */ + case 267: /* trigger_cmd */ + case 271: /* create_vtab */ +{ +delete (yypminor->yy399); +} + break; + case 173: /* explain */ +{ +delete (yypminor->yy225); +} + break; + case 175: /* transtype */ + case 176: /* trans_opt */ +{ +delete (yypminor->yy300); +} + break; + case 177: /* nm */ + case 184: /* table_options */ + case 187: /* columnid */ + case 190: /* id */ + case 191: /* ids */ + case 193: /* typename */ + case 241: /* dbnm */ + case 259: /* collate */ + case 273: /* vtabarg */ + case 274: /* vtabargtoken */ + case 275: /* anylist */ +{ +delete (yypminor->yy211); +} + break; + case 178: /* savepoint_opt */ + case 180: /* ifnotexists */ + case 202: /* autoinc */ + case 210: /* tconscomma */ + case 217: /* ifexists */ + case 252: /* not_opt */ + case 257: /* uniqueflag */ + case 268: /* database_kw_opt */ + case 270: /* kwcolumn_opt */ +{ +delete (yypminor->yy237); +} + break; + case 179: /* temp */ + case 224: /* distinct */ +{ +delete (yypminor->yy376); +} + break; + case 181: /* fullname */ +{ +delete (yypminor->yy66); +} + break; + case 182: /* columnlist */ +{ +delete (yypminor->yy118); +} + break; + case 183: /* conslist_opt */ + case 209: /* conslist */ +{ +delete (yypminor->yy87); +} + break; + case 185: /* select */ + case 220: /* selectnowith */ +{ +delete (yypminor->yy123); +} + break; + case 186: /* column */ +{ +delete (yypminor->yy425); +} + break; + case 188: /* type */ + case 192: /* typetoken */ +{ +delete (yypminor->yy299); +} + break; + case 189: /* carglist */ +{ +delete (yypminor->yy449); +} + break; + case 194: /* signed */ + case 195: /* plus_num */ + case 196: /* minus_num */ + case 198: /* term */ + case 260: /* nmnum */ + case 261: /* number */ +{ +delete (yypminor->yy21); +} + break; + case 197: /* ccons */ +{ +delete (yypminor->yy4); +} + break; + case 199: /* expr */ + case 227: /* where_opt */ + case 229: /* having_opt */ + case 251: /* exprx */ + case 254: /* case_operand */ + case 256: /* case_else */ +{ +delete (yypminor->yy490); +} + break; + case 200: /* onconf */ + case 214: /* resolvetype */ + case 215: /* orconf */ +{ +delete (yypminor->yy30); +} + break; + case 201: /* sortorder */ +{ +delete (yypminor->yy226); +} + break; + case 203: /* idxlist_opt */ + case 212: /* idxlist */ +{ +delete (yypminor->yy139); +} + break; + case 204: /* refargs */ +{ +delete (yypminor->yy108); +} + break; + case 205: /* defer_subclause */ + case 213: /* defer_subclause_opt */ +{ +delete (yypminor->yy131); +} + break; + case 206: /* refarg */ +{ +delete (yypminor->yy271); +} + break; + case 207: /* refact */ +{ +delete (yypminor->yy312); +} + break; + case 208: /* init_deferred_pred_opt */ +{ +delete (yypminor->yy498); +} + break; + case 211: /* tcons */ +{ +delete (yypminor->yy8); +} + break; + case 219: /* with */ + case 276: /* wqlist */ +{ +delete (yypminor->yy367); +} + break; + case 221: /* oneselect */ +{ +delete (yypminor->yy468); +} + break; + case 222: /* multiselect_op */ +{ +delete (yypminor->yy168); +} + break; + case 223: /* values */ +{ +delete (yypminor->yy416); +} + break; + case 225: /* selcollist */ + case 234: /* sclp */ +{ +delete (yypminor->yy263); +} + break; + case 226: /* from */ + case 236: /* joinsrc */ +{ +delete (yypminor->yy373); +} + break; + case 228: /* groupby_opt */ + case 232: /* nexprlist */ + case 233: /* exprlist */ + case 255: /* case_exprlist */ +{ +delete (yypminor->yy13); +} + break; + case 230: /* orderby_opt */ + case 244: /* sortlist */ +{ +delete (yypminor->yy495); +} + break; + case 231: /* limit_opt */ +{ +delete (yypminor->yy128); +} + break; + case 235: /* as */ +{ +delete (yypminor->yy28); +} + break; + case 237: /* singlesrc */ +{ +delete (yypminor->yy173); +} + break; + case 238: /* seltablist */ +{ +delete (yypminor->yy359); +} + break; + case 239: /* joinop */ +{ +delete (yypminor->yy473); +} + break; + case 240: /* joinconstr_opt */ +{ +delete (yypminor->yy117); +} + break; + case 242: /* indexed_opt */ +{ +delete (yypminor->yy472); +} + break; + case 243: /* inscollist */ + case 250: /* inscollist_opt */ + case 272: /* vtabarglist */ +{ +delete (yypminor->yy445); +} + break; + case 247: /* setlist */ +{ +delete (yypminor->yy381); +} + break; + case 249: /* insert_cmd */ +{ +delete (yypminor->yy250); +} + break; + case 253: /* likeop */ +{ +delete (yypminor->yy374); +} + break; + case 258: /* idxlist_single */ +{ +delete (yypminor->yy90); +} + break; + case 262: /* trigger_time */ +{ +delete (yypminor->yy152); +} + break; + case 263: /* trigger_event */ +{ +delete (yypminor->yy309); +} + break; + case 264: /* foreach_clause */ +{ +delete (yypminor->yy409); +} + break; + case 265: /* when_clause */ + case 269: /* key_opt */ +{ +if ((yypminor->yy490)) delete (yypminor->yy490); +} + break; + case 266: /* trigger_cmd_list */ +{ +delete (yypminor->yy214); +} + break; + default: break; /* If no destructor action specified: do nothing */ + } + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + /* There is no mechanism by which the parser stack can be popped below + ** empty in SQLite. */ + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor(pParser, yymajor, &yytos->minor); + delete yytos->tokens; + yytos->tokens = nullptr; + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +**
    +**
  • A pointer to the parser. This should be a pointer +** obtained from sqlite3_parseAlloc. +**
  • A pointer to a function used to reclaim memory obtained +** from malloc. +**
+*/ +void sqlite3_parseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + /* In SQLite, we never try to destroy a parser that was not successfully + ** created in the first place. */ + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int sqlite3_parseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyidxMax; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + GET_CONTEXT; + + if( stateno>YY_SHIFT_COUNT + || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAheaddoFallbacks ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + int stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( i!=YY_REDUCE_USE_DFLT ); + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && iyyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ + + UNUSED_PARAMETER(yypMinor); + parserContext->error(QObject::tr("Parser stack overflow")); + sqlite3_parseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH ){ + yyStackOverflow(yypParser, yypMinor); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz ){ + yyStackOverflow(yypParser, yypMinor); + return; + } + } +#endif + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor = *yypMinor; + yytos->tokens = new QList(); +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { + { 170, 1 }, + { 171, 2 }, + { 171, 1 }, + { 172, 1 }, + { 172, 3 }, + { 173, 0 }, + { 173, 1 }, + { 173, 3 }, + { 174, 1 }, + { 169, 3 }, + { 176, 0 }, + { 176, 1 }, + { 176, 2 }, + { 176, 2 }, + { 175, 0 }, + { 175, 1 }, + { 175, 1 }, + { 175, 1 }, + { 169, 2 }, + { 169, 2 }, + { 169, 2 }, + { 178, 1 }, + { 178, 0 }, + { 169, 2 }, + { 169, 3 }, + { 169, 5 }, + { 169, 2 }, + { 169, 3 }, + { 169, 5 }, + { 169, 10 }, + { 169, 7 }, + { 169, 7 }, + { 169, 5 }, + { 184, 0 }, + { 184, 2 }, + { 184, 2 }, + { 180, 0 }, + { 180, 3 }, + { 179, 1 }, + { 179, 0 }, + { 182, 3 }, + { 182, 1 }, + { 186, 3 }, + { 187, 1 }, + { 187, 1 }, + { 190, 1 }, + { 191, 1 }, + { 177, 1 }, + { 177, 1 }, + { 177, 1 }, + { 188, 0 }, + { 188, 1 }, + { 192, 1 }, + { 192, 4 }, + { 192, 6 }, + { 193, 1 }, + { 193, 2 }, + { 193, 1 }, + { 194, 1 }, + { 194, 1 }, + { 189, 2 }, + { 189, 0 }, + { 197, 2 }, + { 197, 2 }, + { 197, 4 }, + { 197, 3 }, + { 197, 3 }, + { 197, 2 }, + { 197, 2 }, + { 197, 2 }, + { 197, 3 }, + { 197, 5 }, + { 197, 2 }, + { 197, 4 }, + { 197, 4 }, + { 197, 1 }, + { 197, 2 }, + { 197, 2 }, + { 197, 2 }, + { 197, 2 }, + { 197, 3 }, + { 198, 1 }, + { 198, 1 }, + { 198, 1 }, + { 198, 1 }, + { 202, 0 }, + { 202, 1 }, + { 204, 0 }, + { 204, 2 }, + { 206, 2 }, + { 206, 3 }, + { 206, 3 }, + { 206, 3 }, + { 206, 2 }, + { 207, 2 }, + { 207, 2 }, + { 207, 1 }, + { 207, 1 }, + { 207, 2 }, + { 205, 3 }, + { 205, 2 }, + { 208, 0 }, + { 208, 2 }, + { 208, 2 }, + { 183, 0 }, + { 183, 2 }, + { 209, 3 }, + { 209, 1 }, + { 210, 1 }, + { 210, 0 }, + { 211, 2 }, + { 211, 7 }, + { 211, 5 }, + { 211, 5 }, + { 211, 10 }, + { 211, 2 }, + { 211, 7 }, + { 211, 4 }, + { 213, 0 }, + { 213, 1 }, + { 200, 0 }, + { 200, 3 }, + { 215, 0 }, + { 215, 2 }, + { 214, 1 }, + { 214, 1 }, + { 214, 1 }, + { 169, 4 }, + { 169, 6 }, + { 169, 4 }, + { 217, 2 }, + { 217, 0 }, + { 169, 7 }, + { 169, 7 }, + { 169, 5 }, + { 169, 4 }, + { 169, 6 }, + { 169, 4 }, + { 169, 1 }, + { 218, 1 }, + { 185, 2 }, + { 220, 1 }, + { 220, 3 }, + { 220, 1 }, + { 220, 3 }, + { 222, 1 }, + { 222, 2 }, + { 222, 1 }, + { 222, 1 }, + { 221, 9 }, + { 223, 4 }, + { 223, 5 }, + { 224, 1 }, + { 224, 1 }, + { 224, 0 }, + { 234, 2 }, + { 234, 0 }, + { 225, 3 }, + { 225, 2 }, + { 225, 4 }, + { 225, 1 }, + { 225, 4 }, + { 235, 2 }, + { 235, 1 }, + { 235, 2 }, + { 235, 1 }, + { 235, 0 }, + { 226, 0 }, + { 226, 2 }, + { 236, 2 }, + { 236, 0 }, + { 238, 4 }, + { 238, 0 }, + { 237, 4 }, + { 237, 4 }, + { 237, 4 }, + { 237, 0 }, + { 237, 2 }, + { 237, 3 }, + { 237, 1 }, + { 237, 3 }, + { 237, 1 }, + { 240, 2 }, + { 240, 4 }, + { 240, 0 }, + { 241, 0 }, + { 241, 2 }, + { 181, 2 }, + { 239, 1 }, + { 239, 1 }, + { 239, 2 }, + { 239, 3 }, + { 239, 4 }, + { 239, 1 }, + { 242, 0 }, + { 242, 3 }, + { 242, 2 }, + { 242, 3 }, + { 230, 0 }, + { 230, 3 }, + { 244, 4 }, + { 244, 2 }, + { 201, 1 }, + { 201, 1 }, + { 201, 0 }, + { 228, 0 }, + { 228, 3 }, + { 228, 2 }, + { 229, 0 }, + { 229, 2 }, + { 231, 0 }, + { 231, 2 }, + { 231, 4 }, + { 231, 4 }, + { 169, 1 }, + { 245, 6 }, + { 245, 3 }, + { 245, 5 }, + { 245, 6 }, + { 245, 4 }, + { 227, 0 }, + { 227, 2 }, + { 227, 1 }, + { 169, 1 }, + { 246, 8 }, + { 246, 3 }, + { 246, 5 }, + { 246, 6 }, + { 246, 4 }, + { 247, 5 }, + { 247, 3 }, + { 247, 0 }, + { 247, 2 }, + { 247, 3 }, + { 247, 1 }, + { 169, 1 }, + { 248, 6 }, + { 248, 7 }, + { 248, 3 }, + { 248, 5 }, + { 248, 4 }, + { 248, 6 }, + { 249, 2 }, + { 249, 1 }, + { 250, 0 }, + { 250, 3 }, + { 243, 3 }, + { 243, 1 }, + { 243, 0 }, + { 243, 3 }, + { 243, 1 }, + { 251, 1 }, + { 251, 1 }, + { 251, 3 }, + { 251, 1 }, + { 251, 1 }, + { 251, 3 }, + { 251, 5 }, + { 251, 1 }, + { 251, 3 }, + { 251, 6 }, + { 251, 5 }, + { 251, 4 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 4 }, + { 251, 6 }, + { 251, 2 }, + { 251, 3 }, + { 251, 4 }, + { 251, 2 }, + { 251, 2 }, + { 251, 2 }, + { 251, 2 }, + { 251, 6 }, + { 251, 6 }, + { 251, 3 }, + { 251, 6 }, + { 251, 5 }, + { 251, 4 }, + { 251, 5 }, + { 251, 4 }, + { 251, 6 }, + { 251, 2 }, + { 251, 4 }, + { 251, 4 }, + { 251, 4 }, + { 251, 5 }, + { 251, 4 }, + { 251, 6 }, + { 251, 1 }, + { 251, 3 }, + { 251, 5 }, + { 251, 3 }, + { 251, 6 }, + { 199, 1 }, + { 199, 0 }, + { 252, 0 }, + { 252, 1 }, + { 253, 1 }, + { 255, 5 }, + { 255, 4 }, + { 256, 2 }, + { 256, 0 }, + { 254, 1 }, + { 254, 0 }, + { 233, 1 }, + { 233, 0 }, + { 232, 3 }, + { 232, 1 }, + { 169, 12 }, + { 169, 8 }, + { 169, 7 }, + { 169, 5 }, + { 257, 1 }, + { 257, 0 }, + { 203, 0 }, + { 203, 3 }, + { 212, 3 }, + { 212, 1 }, + { 258, 3 }, + { 258, 1 }, + { 259, 0 }, + { 259, 2 }, + { 259, 2 }, + { 169, 4 }, + { 169, 6 }, + { 169, 4 }, + { 169, 1 }, + { 169, 2 }, + { 169, 3 }, + { 169, 5 }, + { 169, 6 }, + { 169, 5 }, + { 169, 6 }, + { 169, 4 }, + { 169, 2 }, + { 260, 1 }, + { 260, 1 }, + { 260, 1 }, + { 260, 1 }, + { 260, 1 }, + { 195, 2 }, + { 195, 1 }, + { 196, 2 }, + { 261, 1 }, + { 261, 1 }, + { 169, 15 }, + { 169, 12 }, + { 169, 14 }, + { 169, 10 }, + { 169, 7 }, + { 169, 5 }, + { 262, 1 }, + { 262, 1 }, + { 262, 2 }, + { 262, 0 }, + { 263, 1 }, + { 263, 1 }, + { 263, 1 }, + { 263, 3 }, + { 264, 0 }, + { 264, 3 }, + { 265, 0 }, + { 265, 2 }, + { 266, 3 }, + { 266, 2 }, + { 266, 1 }, + { 267, 1 }, + { 267, 1 }, + { 267, 1 }, + { 267, 1 }, + { 216, 1 }, + { 169, 4 }, + { 169, 6 }, + { 169, 4 }, + { 169, 6 }, + { 169, 3 }, + { 269, 0 }, + { 269, 2 }, + { 268, 1 }, + { 268, 0 }, + { 169, 1 }, + { 169, 3 }, + { 169, 2 }, + { 169, 4 }, + { 169, 2 }, + { 169, 1 }, + { 169, 3 }, + { 169, 4 }, + { 169, 2 }, + { 169, 6 }, + { 169, 6 }, + { 169, 6 }, + { 169, 5 }, + { 169, 3 }, + { 270, 0 }, + { 270, 1 }, + { 169, 1 }, + { 271, 8 }, + { 271, 11 }, + { 271, 7 }, + { 271, 5 }, + { 272, 1 }, + { 272, 3 }, + { 273, 0 }, + { 273, 2 }, + { 274, 1 }, + { 274, 3 }, + { 275, 0 }, + { 275, 4 }, + { 275, 2 }, + { 219, 0 }, + { 219, 2 }, + { 219, 3 }, + { 276, 6 }, + { 276, 8 }, + { 276, 1 }, +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + sqlite3_parseARG_FETCH; + SqliteStatement* objectForTokens = 0; + QStringList noTokenInheritanceFields; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + /* Silence complaints from purify about yygotominor being uninitialized + ** in some cases when it is copied into the stack after the following + ** switch. yygotominor is uninitialized when a rule reduces that does + ** not set the value of its left-hand side nonterminal. Leaving the + ** value of the nonterminal uninitialized is utterly harmless as long + ** as the value is never used. So really the only thing this code + ** accomplishes is to quieten purify. + ** + ** 2007-01-16: The wireshark project (www.wireshark.org) reports that + ** without this code, their parser segfaults. I'm not sure what there + ** parser is doing to make this happen. This is the second bug report + ** from wireshark this week. Clearly they are stressing Lemon in ways + ** that it has not been previously stressed... (SQLite ticket #2172) + */ + /*memset(&yygotominor, 0, sizeof(yygotominor));*/ + yygotominor = yyzerominor; + + + if (parserContext->executeRules) + { + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ + case 1: /* cmdlist ::= cmdlist ecmd */ +{parserContext->addQuery(yymsp[0].minor.yy399); DONT_INHERIT_TOKENS("cmdlist");} + break; + case 2: /* cmdlist ::= ecmd */ +{parserContext->addQuery(yymsp[0].minor.yy399);} + break; + case 3: /* ecmd ::= SEMI */ +{yygotominor.yy399 = new SqliteEmptyQuery();} + break; + case 4: /* ecmd ::= explain cmdx SEMI */ +{ + yygotominor.yy399 = yymsp[-1].minor.yy399; + yygotominor.yy399->explain = yymsp[-2].minor.yy225->explain; + yygotominor.yy399->queryPlan = yymsp[-2].minor.yy225->queryPlan; + delete yymsp[-2].minor.yy225; + objectForTokens = yygotominor.yy399; + } + break; + case 5: /* explain ::= */ +{yygotominor.yy225 = new ParserStubExplain(false, false);} + break; + case 6: /* explain ::= EXPLAIN */ +{yygotominor.yy225 = new ParserStubExplain(true, false);} + break; + case 7: /* explain ::= EXPLAIN QUERY PLAN */ +{yygotominor.yy225 = new ParserStubExplain(true, true);} + break; + case 8: /* cmdx ::= cmd */ + case 374: /* trigger_cmd ::= update_stmt */ yytestcase(yyruleno==374); + case 375: /* trigger_cmd ::= insert_stmt */ yytestcase(yyruleno==375); + case 376: /* trigger_cmd ::= delete_stmt */ yytestcase(yyruleno==376); + case 377: /* trigger_cmd ::= select_stmt */ yytestcase(yyruleno==377); + case 404: /* cmd ::= create_vtab */ yytestcase(yyruleno==404); +{yygotominor.yy399 = yymsp[0].minor.yy399;} + break; + case 9: /* cmd ::= BEGIN transtype trans_opt */ +{ + yygotominor.yy399 = new SqliteBeginTrans( + yymsp[-1].minor.yy300->type, + yymsp[0].minor.yy300->transactionKw, + yymsp[0].minor.yy300->name + ); + delete yymsp[0].minor.yy300; + delete yymsp[-1].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 10: /* trans_opt ::= */ + case 14: /* transtype ::= */ yytestcase(yyruleno==14); +{yygotominor.yy300 = new ParserStubTransDetails();} + break; + case 11: /* trans_opt ::= TRANSACTION */ +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->transactionKw = true; + } + break; + case 12: /* trans_opt ::= TRANSACTION nm */ + case 13: /* trans_opt ::= TRANSACTION ID_TRANS */ yytestcase(yyruleno==13); +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->transactionKw = true; + yygotominor.yy300->name = *(yymsp[0].minor.yy211); + delete yymsp[0].minor.yy211; + } + break; + case 15: /* transtype ::= DEFERRED */ +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->type = SqliteBeginTrans::Type::DEFERRED; + } + break; + case 16: /* transtype ::= IMMEDIATE */ +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->type = SqliteBeginTrans::Type::IMMEDIATE; + } + break; + case 17: /* transtype ::= EXCLUSIVE */ +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->type = SqliteBeginTrans::Type::EXCLUSIVE; + } + break; + case 18: /* cmd ::= COMMIT trans_opt */ +{ + yygotominor.yy399 = new SqliteCommitTrans( + yymsp[0].minor.yy300->transactionKw, + yymsp[0].minor.yy300->name, + false + ); + delete yymsp[0].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 19: /* cmd ::= END trans_opt */ +{ + yygotominor.yy399 = new SqliteCommitTrans( + yymsp[0].minor.yy300->transactionKw, + yymsp[0].minor.yy300->name, + true + ); + delete yymsp[0].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 20: /* cmd ::= ROLLBACK trans_opt */ +{ + yygotominor.yy399 = new SqliteRollback( + yymsp[0].minor.yy300->transactionKw, + yymsp[0].minor.yy300->name + ); + delete yymsp[0].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 21: /* savepoint_opt ::= SAVEPOINT */ + case 37: /* ifnotexists ::= IF NOT EXISTS */ yytestcase(yyruleno==37); + case 86: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==86); + case 108: /* tconscomma ::= COMMA */ yytestcase(yyruleno==108); + case 130: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==130); + case 304: /* not_opt ::= NOT */ yytestcase(yyruleno==304); + case 320: /* uniqueflag ::= UNIQUE */ yytestcase(yyruleno==320); + case 386: /* database_kw_opt ::= DATABASE */ yytestcase(yyruleno==386); + case 402: /* kwcolumn_opt ::= */ yytestcase(yyruleno==402); +{yygotominor.yy237 = new bool(true);} + break; + case 22: /* savepoint_opt ::= */ + case 36: /* ifnotexists ::= */ yytestcase(yyruleno==36); + case 85: /* autoinc ::= */ yytestcase(yyruleno==85); + case 109: /* tconscomma ::= */ yytestcase(yyruleno==109); + case 131: /* ifexists ::= */ yytestcase(yyruleno==131); + case 303: /* not_opt ::= */ yytestcase(yyruleno==303); + case 321: /* uniqueflag ::= */ yytestcase(yyruleno==321); + case 387: /* database_kw_opt ::= */ yytestcase(yyruleno==387); + case 403: /* kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==403); +{yygotominor.yy237 = new bool(false);} + break; + case 23: /* cmd ::= SAVEPOINT nm */ +{ + yygotominor.yy399 = new SqliteSavepoint(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 24: /* cmd ::= RELEASE savepoint_opt nm */ +{ + yygotominor.yy399 = new SqliteRelease(*(yymsp[-1].minor.yy237), *(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 25: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ + case 26: /* cmd ::= SAVEPOINT ID_TRANS */ yytestcase(yyruleno==26); +{ + yygotominor.yy399 = new SqliteRollback( + yymsp[-3].minor.yy300->transactionKw, + *(yymsp[-1].minor.yy237), + *(yymsp[0].minor.yy211) + ); + delete yymsp[-1].minor.yy237; + delete yymsp[-3].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 27: /* cmd ::= RELEASE savepoint_opt ID_TRANS */ + case 28: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt ID_TRANS */ yytestcase(yyruleno==28); +{ yy_destructor(yypParser,178,&yymsp[-1].minor); +} + break; + case 29: /* cmd ::= CREATE temp TABLE ifnotexists fullname LP columnlist conslist_opt RP table_options */ +{ + yygotominor.yy399 = new SqliteCreateTable( + *(yymsp[-8].minor.yy376), + *(yymsp[-6].minor.yy237), + yymsp[-5].minor.yy66->name1, + yymsp[-5].minor.yy66->name2, + *(yymsp[-3].minor.yy118), + *(yymsp[-2].minor.yy87), + *(yymsp[0].minor.yy211) + ); + delete yymsp[-6].minor.yy237; + delete yymsp[-8].minor.yy376; + delete yymsp[-3].minor.yy118; + delete yymsp[-2].minor.yy87; + delete yymsp[-5].minor.yy66; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 30: /* cmd ::= CREATE temp TABLE ifnotexists fullname AS select */ +{ + yygotominor.yy399 = new SqliteCreateTable( + *(yymsp[-5].minor.yy376), + *(yymsp[-3].minor.yy237), + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + yymsp[0].minor.yy123 + ); + delete yymsp[-3].minor.yy237; + delete yymsp[-5].minor.yy376; + delete yymsp[-2].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 31: /* cmd ::= CREATE temp TABLE ifnotexists nm DOT ID_TAB_NEW */ + case 133: /* cmd ::= CREATE temp VIEW ifnotexists nm DOT ID_VIEW_NEW */ yytestcase(yyruleno==133); + case 357: /* cmd ::= CREATE temp TRIGGER ifnotexists nm DOT ID_TRIG_NEW */ yytestcase(yyruleno==357); +{ yy_destructor(yypParser,179,&yymsp[-5].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 32: /* cmd ::= CREATE temp TABLE ifnotexists ID_DB|ID_TAB_NEW */ + case 134: /* cmd ::= CREATE temp VIEW ifnotexists ID_DB|ID_VIEW_NEW */ yytestcase(yyruleno==134); + case 358: /* cmd ::= CREATE temp TRIGGER ifnotexists ID_DB|ID_TRIG_NEW */ yytestcase(yyruleno==358); +{ yy_destructor(yypParser,179,&yymsp[-3].minor); +} + break; + case 33: /* table_options ::= */ + case 185: /* dbnm ::= */ yytestcase(yyruleno==185); + case 328: /* collate ::= */ yytestcase(yyruleno==328); + case 411: /* vtabarg ::= */ yytestcase(yyruleno==411); + case 415: /* anylist ::= */ yytestcase(yyruleno==415); +{yygotominor.yy211 = new QString();} + break; + case 34: /* table_options ::= WITHOUT nm */ + case 35: /* table_options ::= WITHOUT CTX_ROWID_KW */ yytestcase(yyruleno==35); +{ + if (yymsp[0].minor.yy211->toLower() != "rowid") + parserContext->errorAtToken(QString("Invalid table option: %1").arg(*(yymsp[0].minor.yy211))); + + yygotominor.yy211 = yymsp[0].minor.yy211; + } + break; + case 38: /* temp ::= TEMP */ +{yygotominor.yy376 = new int( (yymsp[0].minor.yy0->value.length() > 4) ? 2 : 1 );} + break; + case 39: /* temp ::= */ + case 154: /* distinct ::= */ yytestcase(yyruleno==154); +{yygotominor.yy376 = new int(0);} + break; + case 40: /* columnlist ::= columnlist COMMA column */ +{ + yymsp[-2].minor.yy118->append(yymsp[0].minor.yy425); + yygotominor.yy118 = yymsp[-2].minor.yy118; + DONT_INHERIT_TOKENS("columnlist"); + } + break; + case 41: /* columnlist ::= column */ +{ + yygotominor.yy118 = new ParserCreateTableColumnList(); + yygotominor.yy118->append(yymsp[0].minor.yy425); + } + break; + case 42: /* column ::= columnid type carglist */ +{ + yygotominor.yy425 = new SqliteCreateTable::Column(*(yymsp[-2].minor.yy211), yymsp[-1].minor.yy299, *(yymsp[0].minor.yy449)); + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy449; + objectForTokens = yygotominor.yy425; + } + break; + case 43: /* columnid ::= nm */ + case 44: /* columnid ::= ID_COL_NEW */ yytestcase(yyruleno==44); + case 47: /* nm ::= id */ yytestcase(yyruleno==47); + case 55: /* typename ::= ids */ yytestcase(yyruleno==55); + case 186: /* dbnm ::= DOT nm */ yytestcase(yyruleno==186); + case 329: /* collate ::= COLLATE ids */ yytestcase(yyruleno==329); + case 330: /* collate ::= COLLATE ID_COLLATE */ yytestcase(yyruleno==330); +{yygotominor.yy211 = yymsp[0].minor.yy211;} + break; + case 45: /* id ::= ID */ +{ + yygotominor.yy211 = new QString( + stripObjName( + yymsp[0].minor.yy0->value, + parserContext->dialect + ) + ); + } + break; + case 46: /* ids ::= ID|STRING */ + case 49: /* nm ::= JOIN_KW */ yytestcase(yyruleno==49); +{yygotominor.yy211 = new QString(yymsp[0].minor.yy0->value);} + break; + case 48: /* nm ::= STRING */ +{yygotominor.yy211 = new QString(stripString(yymsp[0].minor.yy0->value));} + break; + case 50: /* type ::= */ +{yygotominor.yy299 = nullptr;} + break; + case 51: /* type ::= typetoken */ +{yygotominor.yy299 = yymsp[0].minor.yy299;} + break; + case 52: /* typetoken ::= typename */ +{ + yygotominor.yy299 = new SqliteColumnType(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy299; + } + break; + case 53: /* typetoken ::= typename LP signed RP */ +{ + yygotominor.yy299 = new SqliteColumnType(*(yymsp[-3].minor.yy211), *(yymsp[-1].minor.yy21)); + delete yymsp[-3].minor.yy211; + delete yymsp[-1].minor.yy21; + objectForTokens = yygotominor.yy299; + } + break; + case 54: /* typetoken ::= typename LP signed COMMA signed RP */ +{ + yygotominor.yy299 = new SqliteColumnType(*(yymsp[-5].minor.yy211), *(yymsp[-3].minor.yy21), *(yymsp[-1].minor.yy21)); + delete yymsp[-5].minor.yy211; + delete yymsp[-3].minor.yy21; + delete yymsp[-1].minor.yy21; + objectForTokens = yygotominor.yy299; + } + break; + case 56: /* typename ::= typename ids */ + case 57: /* typename ::= ID_COL_TYPE */ yytestcase(yyruleno==57); +{ + yymsp[-1].minor.yy211->append(" " + *(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + yygotominor.yy211 = yymsp[-1].minor.yy211; + } + break; + case 58: /* signed ::= plus_num */ + case 59: /* signed ::= minus_num */ yytestcase(yyruleno==59); + case 343: /* nmnum ::= plus_num */ yytestcase(yyruleno==343); + case 348: /* plus_num ::= PLUS number */ yytestcase(yyruleno==348); + case 349: /* plus_num ::= number */ yytestcase(yyruleno==349); +{yygotominor.yy21 = yymsp[0].minor.yy21;} + break; + case 60: /* carglist ::= carglist ccons */ +{ + yymsp[-1].minor.yy449->append(yymsp[0].minor.yy4); + yygotominor.yy449 = yymsp[-1].minor.yy449; + DONT_INHERIT_TOKENS("carglist"); + } + break; + case 61: /* carglist ::= */ +{yygotominor.yy449 = new ParserCreateTableColumnConstraintList();} + break; + case 62: /* ccons ::= CONSTRAINT nm */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefNameOnly(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy4; + } + break; + case 63: /* ccons ::= DEFAULT term */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefTerm(*(yymsp[0].minor.yy21)); + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy4; + } + break; + case 64: /* ccons ::= DEFAULT LP expr RP */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefExpr(yymsp[-1].minor.yy490); + objectForTokens = yygotominor.yy4; + } + break; + case 65: /* ccons ::= DEFAULT PLUS term */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefTerm(*(yymsp[0].minor.yy21), false); + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy4; + } + break; + case 66: /* ccons ::= DEFAULT MINUS term */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefTerm(*(yymsp[0].minor.yy21), true); + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy4; + } + break; + case 67: /* ccons ::= DEFAULT id */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefId(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy4; + } + break; + case 68: /* ccons ::= DEFAULT CTIME_KW */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefCTime(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy4; + } + break; + case 69: /* ccons ::= NULL onconf */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initNull(*(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + objectForTokens = yygotominor.yy4; + } + break; + case 70: /* ccons ::= NOT NULL onconf */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initNotNull(*(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + objectForTokens = yygotominor.yy4; + } + break; + case 71: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initPk(*(yymsp[-2].minor.yy226), *(yymsp[-1].minor.yy30), *(yymsp[0].minor.yy237)); + delete yymsp[-2].minor.yy226; + delete yymsp[0].minor.yy237; + delete yymsp[-1].minor.yy30; + objectForTokens = yygotominor.yy4; + } + break; + case 72: /* ccons ::= UNIQUE onconf */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initUnique(*(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + objectForTokens = yygotominor.yy4; + } + break; + case 73: /* ccons ::= CHECK LP expr RP */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initCheck(yymsp[-1].minor.yy490); + objectForTokens = yygotominor.yy4; + } + break; + case 74: /* ccons ::= REFERENCES nm idxlist_opt refargs */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initFk(*(yymsp[-2].minor.yy211), *(yymsp[-1].minor.yy139), *(yymsp[0].minor.yy108)); + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy108; + delete yymsp[-1].minor.yy139; + objectForTokens = yygotominor.yy4; + } + break; + case 75: /* ccons ::= defer_subclause */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefer(yymsp[0].minor.yy131->initially, yymsp[0].minor.yy131->deferrable); + delete yymsp[0].minor.yy131; + objectForTokens = yygotominor.yy4; + } + break; + case 76: /* ccons ::= COLLATE ids */ + case 77: /* ccons ::= CONSTRAINT ID_CONSTR */ yytestcase(yyruleno==77); + case 78: /* ccons ::= COLLATE ID_COLLATE */ yytestcase(yyruleno==78); + case 79: /* ccons ::= REFERENCES ID_TAB */ yytestcase(yyruleno==79); +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initColl(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy4; + } + break; + case 80: /* ccons ::= CHECK LP RP */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initCheck(); + objectForTokens = yygotominor.yy4; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 81: /* term ::= NULL */ +{ + yygotominor.yy21 = new QVariant(); + } + break; + case 82: /* term ::= INTEGER */ +{ + int base = 10; + if (yymsp[0].minor.yy0->value.startsWith("0x", Qt::CaseInsensitive)) + base = 16; + + yygotominor.yy21 = new QVariant(yymsp[0].minor.yy0->value.toLongLong(nullptr, base)); + } + break; + case 83: /* term ::= FLOAT */ +{ + yygotominor.yy21 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toDouble()); + } + break; + case 84: /* term ::= STRING|BLOB */ + case 345: /* nmnum ::= ON */ yytestcase(yyruleno==345); + case 346: /* nmnum ::= DELETE */ yytestcase(yyruleno==346); + case 347: /* nmnum ::= DEFAULT */ yytestcase(yyruleno==347); +{yygotominor.yy21 = new QVariant(yymsp[0].minor.yy0->value);} + break; + case 87: /* refargs ::= */ +{yygotominor.yy108 = new ParserFkConditionList();} + break; + case 88: /* refargs ::= refargs refarg */ +{ + yymsp[-1].minor.yy108->append(yymsp[0].minor.yy271); + yygotominor.yy108 = yymsp[-1].minor.yy108; + DONT_INHERIT_TOKENS("refargs"); + } + break; + case 89: /* refarg ::= MATCH nm */ +{ + yygotominor.yy271 = new SqliteForeignKey::Condition(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + } + break; + case 90: /* refarg ::= ON INSERT refact */ +{yygotominor.yy271 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::INSERT, *(yymsp[0].minor.yy312)); delete yymsp[0].minor.yy312;} + break; + case 91: /* refarg ::= ON DELETE refact */ +{yygotominor.yy271 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::DELETE, *(yymsp[0].minor.yy312)); delete yymsp[0].minor.yy312;} + break; + case 92: /* refarg ::= ON UPDATE refact */ + case 93: /* refarg ::= MATCH ID_FK_MATCH */ yytestcase(yyruleno==93); +{yygotominor.yy271 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::UPDATE, *(yymsp[0].minor.yy312)); delete yymsp[0].minor.yy312;} + break; + case 94: /* refact ::= SET NULL */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_NULL);} + break; + case 95: /* refact ::= SET DEFAULT */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_DEFAULT);} + break; + case 96: /* refact ::= CASCADE */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::CASCADE);} + break; + case 97: /* refact ::= RESTRICT */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::RESTRICT);} + break; + case 98: /* refact ::= NO ACTION */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::NO_ACTION);} + break; + case 99: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ +{ + yygotominor.yy131 = new ParserDeferSubClause(SqliteDeferrable::NOT_DEFERRABLE, *(yymsp[0].minor.yy498)); + delete yymsp[0].minor.yy498; + } + break; + case 100: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ +{ + yygotominor.yy131 = new ParserDeferSubClause(SqliteDeferrable::DEFERRABLE, *(yymsp[0].minor.yy498)); + delete yymsp[0].minor.yy498; + } + break; + case 101: /* init_deferred_pred_opt ::= */ +{yygotominor.yy498 = new SqliteInitially(SqliteInitially::null);} + break; + case 102: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ +{yygotominor.yy498 = new SqliteInitially(SqliteInitially::DEFERRED);} + break; + case 103: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ +{yygotominor.yy498 = new SqliteInitially(SqliteInitially::IMMEDIATE);} + break; + case 104: /* conslist_opt ::= */ +{yygotominor.yy87 = new ParserCreateTableConstraintList();} + break; + case 105: /* conslist_opt ::= COMMA conslist */ +{yygotominor.yy87 = yymsp[0].minor.yy87;} + break; + case 106: /* conslist ::= conslist tconscomma tcons */ +{ + yymsp[0].minor.yy8->afterComma = *(yymsp[-1].minor.yy237); + yymsp[-2].minor.yy87->append(yymsp[0].minor.yy8); + yygotominor.yy87 = yymsp[-2].minor.yy87; + delete yymsp[-1].minor.yy237; + DONT_INHERIT_TOKENS("conslist"); + } + break; + case 107: /* conslist ::= tcons */ +{ + yygotominor.yy87 = new ParserCreateTableConstraintList(); + yygotominor.yy87->append(yymsp[0].minor.yy8); + } + break; + case 110: /* tcons ::= CONSTRAINT nm */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initNameOnly(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy8; + } + break; + case 111: /* tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initPk(*(yymsp[-3].minor.yy139), *(yymsp[-2].minor.yy237), *(yymsp[0].minor.yy30)); + delete yymsp[-2].minor.yy237; + delete yymsp[0].minor.yy30; + delete yymsp[-3].minor.yy139; + objectForTokens = yygotominor.yy8; + } + break; + case 112: /* tcons ::= UNIQUE LP idxlist RP onconf */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initUnique(*(yymsp[-2].minor.yy139), *(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + delete yymsp[-2].minor.yy139; + objectForTokens = yygotominor.yy8; + } + break; + case 113: /* tcons ::= CHECK LP expr RP onconf */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initCheck(yymsp[-2].minor.yy490, *(yymsp[0].minor.yy30)); + objectForTokens = yygotominor.yy8; + } + break; + case 114: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt */ + case 115: /* tcons ::= CONSTRAINT ID_CONSTR */ yytestcase(yyruleno==115); + case 116: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES ID_TAB */ yytestcase(yyruleno==116); +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initFk( + *(yymsp[-6].minor.yy139), + *(yymsp[-3].minor.yy211), + *(yymsp[-2].minor.yy139), + *(yymsp[-1].minor.yy108), + yymsp[0].minor.yy131->initially, + yymsp[0].minor.yy131->deferrable + ); + delete yymsp[-3].minor.yy211; + delete yymsp[-1].minor.yy108; + delete yymsp[0].minor.yy131; + delete yymsp[-2].minor.yy139; + delete yymsp[-6].minor.yy139; + objectForTokens = yygotominor.yy8; + } + break; + case 117: /* tcons ::= CHECK LP RP onconf */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initCheck(); + objectForTokens = yygotominor.yy8; + parserContext->minorErrorAfterLastToken("Syntax error"); + yy_destructor(yypParser,200,&yymsp[0].minor); +} + break; + case 118: /* defer_subclause_opt ::= */ +{yygotominor.yy131 = new ParserDeferSubClause(SqliteDeferrable::null, SqliteInitially::null);} + break; + case 119: /* defer_subclause_opt ::= defer_subclause */ +{yygotominor.yy131 = yymsp[0].minor.yy131;} + break; + case 120: /* onconf ::= */ + case 122: /* orconf ::= */ yytestcase(yyruleno==122); +{yygotominor.yy30 = new SqliteConflictAlgo(SqliteConflictAlgo::null);} + break; + case 121: /* onconf ::= ON CONFLICT resolvetype */ + case 123: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==123); +{yygotominor.yy30 = yymsp[0].minor.yy30;} + break; + case 124: /* resolvetype ::= raisetype */ + case 125: /* resolvetype ::= IGNORE */ yytestcase(yyruleno==125); + case 126: /* resolvetype ::= REPLACE */ yytestcase(yyruleno==126); +{yygotominor.yy30 = new SqliteConflictAlgo(sqliteConflictAlgo(yymsp[0].minor.yy0->value));} + break; + case 127: /* cmd ::= DROP TABLE ifexists fullname */ +{ + yygotominor.yy399 = new SqliteDropTable(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy66->name1, yymsp[0].minor.yy66->name2); + delete yymsp[-1].minor.yy237; + delete yymsp[0].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 128: /* cmd ::= DROP TABLE ifexists nm DOT ID_TAB */ + case 129: /* cmd ::= DROP TABLE ifexists ID_DB|ID_TAB */ yytestcase(yyruleno==129); + case 136: /* cmd ::= DROP VIEW ifexists nm DOT ID_VIEW */ yytestcase(yyruleno==136); + case 137: /* cmd ::= DROP VIEW ifexists ID_DB|ID_VIEW */ yytestcase(yyruleno==137); + case 178: /* singlesrc ::= nm DOT ID_TAB */ yytestcase(yyruleno==178); + case 179: /* singlesrc ::= ID_DB|ID_TAB */ yytestcase(yyruleno==179); + case 180: /* singlesrc ::= nm DOT ID_VIEW */ yytestcase(yyruleno==180); + case 181: /* singlesrc ::= ID_DB|ID_VIEW */ yytestcase(yyruleno==181); + case 297: /* exprx ::= nm DOT ID_TAB|ID_COL */ yytestcase(yyruleno==297); + case 318: /* cmd ::= CREATE uniqueflag INDEX ifnotexists nm DOT ID_IDX_NEW */ yytestcase(yyruleno==318); + case 319: /* cmd ::= CREATE uniqueflag INDEX ifnotexists ID_DB|ID_IDX_NEW */ yytestcase(yyruleno==319); + case 332: /* cmd ::= DROP INDEX ifexists nm DOT ID_IDX */ yytestcase(yyruleno==332); + case 333: /* cmd ::= DROP INDEX ifexists ID_DB|ID_IDX */ yytestcase(yyruleno==333); + case 341: /* cmd ::= PRAGMA nm DOT ID_PRAGMA */ yytestcase(yyruleno==341); + case 342: /* cmd ::= PRAGMA ID_DB|ID_PRAGMA */ yytestcase(yyruleno==342); + case 380: /* cmd ::= DROP TRIGGER ifexists nm DOT ID_TRIG */ yytestcase(yyruleno==380); + case 381: /* cmd ::= DROP TRIGGER ifexists ID_DB|ID_TRIG */ yytestcase(yyruleno==381); + case 391: /* cmd ::= REINDEX nm DOT ID_TAB|ID_IDX */ yytestcase(yyruleno==391); + case 392: /* cmd ::= REINDEX ID_DB|ID_IDX|ID_TAB */ yytestcase(yyruleno==392); + case 395: /* cmd ::= ANALYZE nm DOT ID_TAB|ID_IDX */ yytestcase(yyruleno==395); + case 396: /* cmd ::= ANALYZE ID_DB|ID_IDX|ID_TAB */ yytestcase(yyruleno==396); + case 400: /* cmd ::= ALTER TABLE nm DOT ID_TAB */ yytestcase(yyruleno==400); + case 401: /* cmd ::= ALTER TABLE ID_DB|ID_TAB */ yytestcase(yyruleno==401); + case 407: /* create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm DOT ID_TAB_NEW */ yytestcase(yyruleno==407); + case 408: /* create_vtab ::= CREATE VIRTUAL TABLE ifnotexists ID_DB|ID_TAB_NEW */ yytestcase(yyruleno==408); +{ yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 132: /* cmd ::= CREATE temp VIEW ifnotexists fullname AS select */ +{ + yygotominor.yy399 = new SqliteCreateView(*(yymsp[-5].minor.yy376), *(yymsp[-3].minor.yy237), yymsp[-2].minor.yy66->name1, yymsp[-2].minor.yy66->name2, yymsp[0].minor.yy123); + delete yymsp[-5].minor.yy376; + delete yymsp[-3].minor.yy237; + delete yymsp[-2].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 135: /* cmd ::= DROP VIEW ifexists fullname */ +{ + yygotominor.yy399 = new SqliteDropView(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy66->name1, yymsp[0].minor.yy66->name2); + delete yymsp[-1].minor.yy237; + delete yymsp[0].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 138: /* cmd ::= select_stmt */ + case 214: /* cmd ::= delete_stmt */ yytestcase(yyruleno==214); + case 223: /* cmd ::= update_stmt */ yytestcase(yyruleno==223); + case 235: /* cmd ::= insert_stmt */ yytestcase(yyruleno==235); +{ + yygotominor.yy399 = yymsp[0].minor.yy399; + objectForTokens = yygotominor.yy399; + } + break; + case 139: /* select_stmt ::= select */ +{ + yygotominor.yy399 = yymsp[0].minor.yy123; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 140: /* select ::= with selectnowith */ +{ + yygotominor.yy123 = yymsp[0].minor.yy123; + yymsp[0].minor.yy123->setWith(yymsp[-1].minor.yy367); + objectForTokens = yygotominor.yy123; + } + break; + case 141: /* selectnowith ::= oneselect */ +{ + yygotominor.yy123 = SqliteSelect::append(yymsp[0].minor.yy468); + objectForTokens = yygotominor.yy123; + } + break; + case 142: /* selectnowith ::= selectnowith multiselect_op oneselect */ +{ + yygotominor.yy123 = SqliteSelect::append(yymsp[-2].minor.yy123, *(yymsp[-1].minor.yy168), yymsp[0].minor.yy468); + delete yymsp[-1].minor.yy168; + objectForTokens = yygotominor.yy123; + } + break; + case 143: /* selectnowith ::= values */ +{ + yygotominor.yy123 = SqliteSelect::append(*(yymsp[0].minor.yy416)); + delete yymsp[0].minor.yy416; + objectForTokens = yygotominor.yy123; + } + break; + case 144: /* selectnowith ::= selectnowith COMMA values */ +{ + yygotominor.yy123 = SqliteSelect::append(yymsp[-2].minor.yy123, SqliteSelect::CompoundOperator::UNION_ALL, *(yymsp[0].minor.yy416)); + delete yymsp[0].minor.yy416; + objectForTokens = yygotominor.yy123; + } + break; + case 145: /* multiselect_op ::= UNION */ +{yygotominor.yy168 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION);} + break; + case 146: /* multiselect_op ::= UNION ALL */ +{yygotominor.yy168 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION_ALL);} + break; + case 147: /* multiselect_op ::= EXCEPT */ +{yygotominor.yy168 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::EXCEPT);} + break; + case 148: /* multiselect_op ::= INTERSECT */ +{yygotominor.yy168 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::INTERSECT);} + break; + case 149: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ +{ + yygotominor.yy468 = new SqliteSelect::Core( + *(yymsp[-7].minor.yy376), + *(yymsp[-6].minor.yy263), + yymsp[-5].minor.yy373, + yymsp[-4].minor.yy490, + *(yymsp[-3].minor.yy13), + yymsp[-2].minor.yy490, + *(yymsp[-1].minor.yy495), + yymsp[0].minor.yy128 + ); + delete yymsp[-6].minor.yy263; + delete yymsp[-7].minor.yy376; + delete yymsp[-3].minor.yy13; + delete yymsp[-1].minor.yy495; + objectForTokens = yygotominor.yy468; + } + break; + case 150: /* values ::= VALUES LP nexprlist RP */ +{ + yygotominor.yy416 = new ParserExprNestedList(); + yygotominor.yy416->append(*(yymsp[-1].minor.yy13)); + delete yymsp[-1].minor.yy13; + } + break; + case 151: /* values ::= values COMMA LP exprlist RP */ +{ + yymsp[-4].minor.yy416->append(*(yymsp[-1].minor.yy13)); + yygotominor.yy416 = yymsp[-4].minor.yy416; + delete yymsp[-1].minor.yy13; + DONT_INHERIT_TOKENS("values"); + } + break; + case 152: /* distinct ::= DISTINCT */ +{yygotominor.yy376 = new int(1);} + break; + case 153: /* distinct ::= ALL */ +{yygotominor.yy376 = new int(2);} + break; + case 155: /* sclp ::= selcollist COMMA */ +{yygotominor.yy263 = yymsp[-1].minor.yy263;} + break; + case 156: /* sclp ::= */ +{yygotominor.yy263 = new ParserResultColumnList();} + break; + case 157: /* selcollist ::= sclp expr as */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + yymsp[-1].minor.yy490, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->asKw : false, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->name : QString::null + ); + + yymsp[-2].minor.yy263->append(obj); + yygotominor.yy263 = yymsp[-2].minor.yy263; + delete yymsp[0].minor.yy28; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 158: /* selcollist ::= sclp STAR */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn(true); + + yymsp[-1].minor.yy263->append(obj); + yygotominor.yy263 = yymsp[-1].minor.yy263; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 159: /* selcollist ::= sclp nm DOT STAR */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + true, + *(yymsp[-2].minor.yy211) + ); + yymsp[-3].minor.yy263->append(obj); + yygotominor.yy263 = yymsp[-3].minor.yy263; + delete yymsp[-2].minor.yy211; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 160: /* selcollist ::= sclp */ + case 161: /* selcollist ::= sclp ID_TAB DOT STAR */ yytestcase(yyruleno==161); +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy263 = yymsp[0].minor.yy263; + } + break; + case 162: /* as ::= AS nm */ +{ + yygotominor.yy28 = new ParserStubAlias(*(yymsp[0].minor.yy211), true); + delete yymsp[0].minor.yy211; + } + break; + case 163: /* as ::= ids */ + case 164: /* as ::= AS ID_ALIAS */ yytestcase(yyruleno==164); + case 165: /* as ::= ID_ALIAS */ yytestcase(yyruleno==165); +{ + yygotominor.yy28 = new ParserStubAlias(*(yymsp[0].minor.yy211), false); + delete yymsp[0].minor.yy211; + } + break; + case 166: /* as ::= */ +{yygotominor.yy28 = nullptr;} + break; + case 167: /* from ::= */ +{yygotominor.yy373 = nullptr;} + break; + case 168: /* from ::= FROM joinsrc */ +{yygotominor.yy373 = yymsp[0].minor.yy373;} + break; + case 169: /* joinsrc ::= singlesrc seltablist */ +{ + yygotominor.yy373 = new SqliteSelect::Core::JoinSource( + yymsp[-1].minor.yy173, + *(yymsp[0].minor.yy359) + ); + delete yymsp[0].minor.yy359; + objectForTokens = yygotominor.yy373; + } + break; + case 170: /* joinsrc ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy373 = new SqliteSelect::Core::JoinSource(); + objectForTokens = yygotominor.yy373; + } + break; + case 171: /* seltablist ::= seltablist joinop singlesrc joinconstr_opt */ +{ + SqliteSelect::Core::JoinSourceOther* src = + new SqliteSelect::Core::JoinSourceOther(yymsp[-2].minor.yy473, yymsp[-1].minor.yy173, yymsp[0].minor.yy117); + + yymsp[-3].minor.yy359->append(src); + yygotominor.yy359 = yymsp[-3].minor.yy359; + objectForTokens = src; + DONT_INHERIT_TOKENS("seltablist"); + } + break; + case 172: /* seltablist ::= */ +{ + yygotominor.yy359 = new ParserOtherSourceList(); + } + break; + case 173: /* singlesrc ::= nm dbnm as indexed_opt */ +{ + yygotominor.yy173 = new SqliteSelect::Core::SingleSource( + *(yymsp[-3].minor.yy211), + *(yymsp[-2].minor.yy211), + yymsp[-1].minor.yy28 ? yymsp[-1].minor.yy28->asKw : false, + yymsp[-1].minor.yy28 ? yymsp[-1].minor.yy28->name : QString::null, + yymsp[0].minor.yy472 ? yymsp[0].minor.yy472->notIndexedKw : false, + yymsp[0].minor.yy472 ? yymsp[0].minor.yy472->indexedBy : QString::null + ); + delete yymsp[-3].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[-1].minor.yy28; + if (yymsp[0].minor.yy472) + delete yymsp[0].minor.yy472; + objectForTokens = yygotominor.yy173; + } + break; + case 174: /* singlesrc ::= LP select RP as */ +{ + yygotominor.yy173 = new SqliteSelect::Core::SingleSource( + yymsp[-2].minor.yy123, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->asKw : false, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->name : QString::null + ); + delete yymsp[0].minor.yy28; + objectForTokens = yygotominor.yy173; + } + break; + case 175: /* singlesrc ::= LP joinsrc RP as */ +{ + yygotominor.yy173 = new SqliteSelect::Core::SingleSource( + yymsp[-2].minor.yy373, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->asKw : false, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->name : QString::null + ); + delete yymsp[0].minor.yy28; + objectForTokens = yygotominor.yy173; + } + break; + case 176: /* singlesrc ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy173 = new SqliteSelect::Core::SingleSource(); + objectForTokens = yygotominor.yy173; + } + break; + case 177: /* singlesrc ::= nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy173 = new SqliteSelect::Core::SingleSource(); + yygotominor.yy173->database = *(yymsp[-1].minor.yy211); + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy173; + } + break; + case 182: /* joinconstr_opt ::= ON expr */ +{ + yygotominor.yy117 = new SqliteSelect::Core::JoinConstraint(yymsp[0].minor.yy490); + objectForTokens = yygotominor.yy117; + } + break; + case 183: /* joinconstr_opt ::= USING LP inscollist RP */ +{ + yygotominor.yy117 = new SqliteSelect::Core::JoinConstraint(*(yymsp[-1].minor.yy445)); + delete yymsp[-1].minor.yy445; + objectForTokens = yygotominor.yy117; + } + break; + case 184: /* joinconstr_opt ::= */ +{yygotominor.yy117 = nullptr;} + break; + case 187: /* fullname ::= nm dbnm */ +{ + yygotominor.yy66 = new ParserFullName(); + yygotominor.yy66->name1 = *(yymsp[-1].minor.yy211); + yygotominor.yy66->name2 = *(yymsp[0].minor.yy211); + delete yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + } + break; + case 188: /* joinop ::= COMMA */ +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(true); + objectForTokens = yygotominor.yy473; + } + break; + case 189: /* joinop ::= JOIN */ +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(false); + objectForTokens = yygotominor.yy473; + } + break; + case 190: /* joinop ::= JOIN_KW JOIN */ +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy473; + } + break; + case 191: /* joinop ::= JOIN_KW nm JOIN */ +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(yymsp[-2].minor.yy0->value, *(yymsp[-1].minor.yy211)); + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy473; + } + break; + case 192: /* joinop ::= JOIN_KW nm nm JOIN */ + case 193: /* joinop ::= ID_JOIN_OPTS */ yytestcase(yyruleno==193); +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(yymsp[-3].minor.yy0->value, *(yymsp[-2].minor.yy211), *(yymsp[-1].minor.yy211)); + delete yymsp[-2].minor.yy211; + delete yymsp[-2].minor.yy211; + objectForTokens = yygotominor.yy473; + } + break; + case 194: /* indexed_opt ::= */ +{yygotominor.yy472 = nullptr;} + break; + case 195: /* indexed_opt ::= INDEXED BY nm */ +{ + yygotominor.yy472 = new ParserIndexedBy(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + } + break; + case 196: /* indexed_opt ::= NOT INDEXED */ + case 197: /* indexed_opt ::= INDEXED BY ID_IDX */ yytestcase(yyruleno==197); +{yygotominor.yy472 = new ParserIndexedBy(true);} + break; + case 198: /* orderby_opt ::= */ +{yygotominor.yy495 = new ParserOrderByList();} + break; + case 199: /* orderby_opt ::= ORDER BY sortlist */ +{yygotominor.yy495 = yymsp[0].minor.yy495;} + break; + case 200: /* sortlist ::= sortlist COMMA expr sortorder */ +{ + SqliteOrderBy* obj = new SqliteOrderBy(yymsp[-1].minor.yy490, *(yymsp[0].minor.yy226)); + yymsp[-3].minor.yy495->append(obj); + yygotominor.yy495 = yymsp[-3].minor.yy495; + delete yymsp[0].minor.yy226; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sortlist"); + } + break; + case 201: /* sortlist ::= expr sortorder */ +{ + SqliteOrderBy* obj = new SqliteOrderBy(yymsp[-1].minor.yy490, *(yymsp[0].minor.yy226)); + yygotominor.yy495 = new ParserOrderByList(); + yygotominor.yy495->append(obj); + delete yymsp[0].minor.yy226; + objectForTokens = obj; + } + break; + case 202: /* sortorder ::= ASC */ +{yygotominor.yy226 = new SqliteSortOrder(SqliteSortOrder::ASC);} + break; + case 203: /* sortorder ::= DESC */ +{yygotominor.yy226 = new SqliteSortOrder(SqliteSortOrder::DESC);} + break; + case 204: /* sortorder ::= */ +{yygotominor.yy226 = new SqliteSortOrder(SqliteSortOrder::null);} + break; + case 205: /* groupby_opt ::= */ + case 313: /* exprlist ::= */ yytestcase(yyruleno==313); +{yygotominor.yy13 = new ParserExprList();} + break; + case 206: /* groupby_opt ::= GROUP BY nexprlist */ + case 312: /* exprlist ::= nexprlist */ yytestcase(yyruleno==312); +{yygotominor.yy13 = yymsp[0].minor.yy13;} + break; + case 207: /* groupby_opt ::= GROUP BY */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy13 = new ParserExprList(); + } + break; + case 208: /* having_opt ::= */ + case 220: /* where_opt ::= */ yytestcase(yyruleno==220); + case 309: /* case_else ::= */ yytestcase(yyruleno==309); + case 311: /* case_operand ::= */ yytestcase(yyruleno==311); + case 369: /* when_clause ::= */ yytestcase(yyruleno==369); + case 384: /* key_opt ::= */ yytestcase(yyruleno==384); +{yygotominor.yy490 = nullptr;} + break; + case 209: /* having_opt ::= HAVING expr */ + case 221: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==221); + case 301: /* expr ::= exprx */ yytestcase(yyruleno==301); + case 308: /* case_else ::= ELSE expr */ yytestcase(yyruleno==308); + case 310: /* case_operand ::= exprx */ yytestcase(yyruleno==310); + case 370: /* when_clause ::= WHEN expr */ yytestcase(yyruleno==370); + case 385: /* key_opt ::= KEY expr */ yytestcase(yyruleno==385); +{yygotominor.yy490 = yymsp[0].minor.yy490;} + break; + case 210: /* limit_opt ::= */ +{yygotominor.yy128 = nullptr;} + break; + case 211: /* limit_opt ::= LIMIT expr */ +{ + yygotominor.yy128 = new SqliteLimit(yymsp[0].minor.yy490); + objectForTokens = yygotominor.yy128; + } + break; + case 212: /* limit_opt ::= LIMIT expr OFFSET expr */ +{ + yygotominor.yy128 = new SqliteLimit(yymsp[-2].minor.yy490, yymsp[0].minor.yy490, true); + objectForTokens = yygotominor.yy128; + } + break; + case 213: /* limit_opt ::= LIMIT expr COMMA expr */ +{ + yygotominor.yy128 = new SqliteLimit(yymsp[-2].minor.yy490, yymsp[0].minor.yy490, false); + objectForTokens = yygotominor.yy128; + } + break; + case 215: /* delete_stmt ::= with DELETE FROM fullname indexed_opt where_opt */ +{ + if (yymsp[-1].minor.yy472) + { + if (!yymsp[-1].minor.yy472->indexedBy.isNull()) + { + yygotominor.yy399 = new SqliteDelete( + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + yymsp[-1].minor.yy472->indexedBy, + yymsp[0].minor.yy490, + yymsp[-5].minor.yy367 + ); + } + else + { + yygotominor.yy399 = new SqliteDelete( + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + yymsp[-1].minor.yy472->notIndexedKw, + yymsp[0].minor.yy490, + yymsp[-5].minor.yy367 + ); + } + delete yymsp[-1].minor.yy472; + } + else + { + yygotominor.yy399 = new SqliteDelete( + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + false, + yymsp[0].minor.yy490, + yymsp[-5].minor.yy367 + ); + } + delete yymsp[-2].minor.yy66; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 216: /* delete_stmt ::= with DELETE FROM */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->with = yymsp[-2].minor.yy367; + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + } + break; + case 217: /* delete_stmt ::= with DELETE FROM nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->with = yymsp[-4].minor.yy367; + q->database = *(yymsp[-1].minor.yy211); + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[-1].minor.yy211; + } + break; + case 218: /* delete_stmt ::= with DELETE FROM nm DOT ID_TAB */ + case 227: /* update_stmt ::= with UPDATE orconf nm DOT ID_TAB */ yytestcase(yyruleno==227); +{ yy_destructor(yypParser,219,&yymsp[-5].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 219: /* delete_stmt ::= with DELETE FROM ID_DB|ID_TAB */ + case 228: /* update_stmt ::= with UPDATE orconf ID_DB|ID_TAB */ yytestcase(yyruleno==228); +{ yy_destructor(yypParser,219,&yymsp[-3].minor); +} + break; + case 222: /* where_opt ::= WHERE */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy490 = new SqliteExpr(); + } + break; + case 224: /* update_stmt ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt */ +{ + yygotominor.yy399 = new SqliteUpdate( + *(yymsp[-5].minor.yy30), + yymsp[-4].minor.yy66->name1, + yymsp[-4].minor.yy66->name2, + yymsp[-3].minor.yy472 ? yymsp[-3].minor.yy472->notIndexedKw : false, + yymsp[-3].minor.yy472 ? yymsp[-3].minor.yy472->indexedBy : QString::null, + *(yymsp[-1].minor.yy381), + yymsp[0].minor.yy490, + yymsp[-7].minor.yy367 + ); + delete yymsp[-5].minor.yy30; + delete yymsp[-4].minor.yy66; + delete yymsp[-1].minor.yy381; + if (yymsp[-3].minor.yy472) + delete yymsp[-3].minor.yy472; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 225: /* update_stmt ::= with UPDATE orconf */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->with = yymsp[-2].minor.yy367; + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[0].minor.yy30; + } + break; + case 226: /* update_stmt ::= with UPDATE orconf nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->with = yymsp[-4].minor.yy367; + q->database = *(yymsp[-1].minor.yy211); + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[-2].minor.yy30; + delete yymsp[-1].minor.yy211; + } + break; + case 229: /* setlist ::= setlist COMMA nm EQ expr */ +{ + yymsp[-4].minor.yy381->append(ParserSetValue(*(yymsp[-2].minor.yy211), yymsp[0].minor.yy490)); + yygotominor.yy381 = yymsp[-4].minor.yy381; + delete yymsp[-2].minor.yy211; + DONT_INHERIT_TOKENS("setlist"); + } + break; + case 230: /* setlist ::= nm EQ expr */ +{ + yygotominor.yy381 = new ParserSetValueList(); + yygotominor.yy381->append(ParserSetValue(*(yymsp[-2].minor.yy211), yymsp[0].minor.yy490)); + delete yymsp[-2].minor.yy211; + } + break; + case 231: /* setlist ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy381 = new ParserSetValueList(); + } + break; + case 232: /* setlist ::= setlist COMMA */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy381 = yymsp[-1].minor.yy381; + } + break; + case 233: /* setlist ::= setlist COMMA ID_COL */ + case 234: /* setlist ::= ID_COL */ yytestcase(yyruleno==234); +{ yy_destructor(yypParser,247,&yymsp[-2].minor); +} + break; + case 236: /* insert_stmt ::= with insert_cmd INTO fullname inscollist_opt select */ +{ + yygotominor.yy399 = new SqliteInsert( + yymsp[-4].minor.yy250->replace, + yymsp[-4].minor.yy250->orConflict, + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + *(yymsp[-1].minor.yy445), + yymsp[0].minor.yy123, + yymsp[-5].minor.yy367 + ); + delete yymsp[-2].minor.yy66; + delete yymsp[-4].minor.yy250; + delete yymsp[-1].minor.yy445; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 237: /* insert_stmt ::= with insert_cmd INTO fullname inscollist_opt DEFAULT VALUES */ +{ + yygotominor.yy399 = new SqliteInsert( + yymsp[-5].minor.yy250->replace, + yymsp[-5].minor.yy250->orConflict, + yymsp[-3].minor.yy66->name1, + yymsp[-3].minor.yy66->name2, + *(yymsp[-2].minor.yy445), + yymsp[-6].minor.yy367 + ); + delete yymsp[-3].minor.yy66; + delete yymsp[-5].minor.yy250; + delete yymsp[-2].minor.yy445; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 238: /* insert_stmt ::= with insert_cmd INTO */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = yymsp[-1].minor.yy250->replace; + q->onConflict = yymsp[-1].minor.yy250->orConflict; + q->with = yymsp[-2].minor.yy367; + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[-1].minor.yy250; + } + break; + case 239: /* insert_stmt ::= with insert_cmd INTO nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = yymsp[-3].minor.yy250->replace; + q->onConflict = yymsp[-3].minor.yy250->orConflict; + q->with = yymsp[-4].minor.yy367; + q->database = *(yymsp[-1].minor.yy211); + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[-3].minor.yy250; + delete yymsp[-1].minor.yy211; + } + break; + case 240: /* insert_stmt ::= with insert_cmd INTO ID_DB|ID_TAB */ +{ yy_destructor(yypParser,219,&yymsp[-3].minor); + yy_destructor(yypParser,249,&yymsp[-2].minor); +} + break; + case 241: /* insert_stmt ::= with insert_cmd INTO nm DOT ID_TAB */ +{ yy_destructor(yypParser,219,&yymsp[-5].minor); + yy_destructor(yypParser,249,&yymsp[-4].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 242: /* insert_cmd ::= INSERT orconf */ +{ + yygotominor.yy250 = new ParserStubInsertOrReplace(false, *(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + } + break; + case 243: /* insert_cmd ::= REPLACE */ +{yygotominor.yy250 = new ParserStubInsertOrReplace(true);} + break; + case 244: /* inscollist_opt ::= */ +{yygotominor.yy445 = new ParserStringList();} + break; + case 245: /* inscollist_opt ::= LP inscollist RP */ +{yygotominor.yy445 = yymsp[-1].minor.yy445;} + break; + case 246: /* inscollist ::= inscollist COMMA nm */ +{ + yymsp[-2].minor.yy445->append(*(yymsp[0].minor.yy211)); + yygotominor.yy445 = yymsp[-2].minor.yy445; + delete yymsp[0].minor.yy211; + DONT_INHERIT_TOKENS("inscollist"); + } + break; + case 247: /* inscollist ::= nm */ +{ + yygotominor.yy445 = new ParserStringList(); + yygotominor.yy445->append(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + } + break; + case 248: /* inscollist ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy445 = new ParserStringList(); + } + break; + case 249: /* inscollist ::= inscollist COMMA ID_COL */ + case 250: /* inscollist ::= ID_COL */ yytestcase(yyruleno==250); +{ yy_destructor(yypParser,243,&yymsp[-2].minor); +} + break; + case 251: /* exprx ::= term */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initLiteral(*(yymsp[0].minor.yy21)); + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy490; + } + break; + case 252: /* exprx ::= CTIME_KW */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initCTime(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 253: /* exprx ::= LP expr RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initSubExpr(yymsp[-1].minor.yy490); + objectForTokens = yygotominor.yy490; + } + break; + case 254: /* exprx ::= id */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 255: /* exprx ::= JOIN_KW */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 256: /* exprx ::= nm DOT nm */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[-2].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 257: /* exprx ::= nm DOT nm DOT nm */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[-4].minor.yy211), *(yymsp[-2].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-4].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 258: /* exprx ::= VARIABLE */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initBindParam(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 259: /* exprx ::= expr COLLATE ids */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initCollate(yymsp[-2].minor.yy490, *(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 260: /* exprx ::= CAST LP expr AS typetoken RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initCast(yymsp[-3].minor.yy490, yymsp[-1].minor.yy299); + objectForTokens = yygotominor.yy490; + } + break; + case 261: /* exprx ::= ID LP distinct exprlist RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initFunction(yymsp[-4].minor.yy0->value, *(yymsp[-2].minor.yy376), *(yymsp[-1].minor.yy13)); + delete yymsp[-2].minor.yy376; + delete yymsp[-1].minor.yy13; + objectForTokens = yygotominor.yy490; + } + break; + case 262: /* exprx ::= ID LP STAR RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initFunction(yymsp[-3].minor.yy0->value, true); + objectForTokens = yygotominor.yy490; + } + break; + case 263: /* exprx ::= expr AND expr */ + case 264: /* exprx ::= expr OR expr */ yytestcase(yyruleno==264); + case 265: /* exprx ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==265); + case 266: /* exprx ::= expr EQ|NE expr */ yytestcase(yyruleno==266); + case 267: /* exprx ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==267); + case 268: /* exprx ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==268); + case 269: /* exprx ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==269); + case 270: /* exprx ::= expr CONCAT expr */ yytestcase(yyruleno==270); +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initBinOp(yymsp[-2].minor.yy490, yymsp[-1].minor.yy0->value, yymsp[0].minor.yy490); + objectForTokens = yygotominor.yy490; + } + break; + case 271: /* exprx ::= expr not_opt likeop expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initLike(yymsp[-3].minor.yy490, *(yymsp[-2].minor.yy237), *(yymsp[-1].minor.yy374), yymsp[0].minor.yy490); + delete yymsp[-2].minor.yy237; + delete yymsp[-1].minor.yy374; + objectForTokens = yygotominor.yy490; + } + break; + case 272: /* exprx ::= expr not_opt likeop expr ESCAPE expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initLike(yymsp[-5].minor.yy490, *(yymsp[-4].minor.yy237), *(yymsp[-3].minor.yy374), yymsp[-2].minor.yy490, yymsp[0].minor.yy490); + delete yymsp[-4].minor.yy237; + delete yymsp[-3].minor.yy374; + objectForTokens = yygotominor.yy490; + } + break; + case 273: /* exprx ::= expr ISNULL|NOTNULL */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initNull(yymsp[-1].minor.yy490, yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 274: /* exprx ::= expr NOT NULL */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initNull(yymsp[-2].minor.yy490, "NOT NULL"); + objectForTokens = yygotominor.yy490; + } + break; + case 275: /* exprx ::= expr IS not_opt expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initIs(yymsp[-3].minor.yy490, *(yymsp[-1].minor.yy237), yymsp[0].minor.yy490); + delete yymsp[-1].minor.yy237; + objectForTokens = yygotominor.yy490; + } + break; + case 276: /* exprx ::= NOT expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initUnaryOp(yymsp[0].minor.yy490, yymsp[-1].minor.yy0->value); + } + break; + case 277: /* exprx ::= BITNOT expr */ + case 278: /* exprx ::= MINUS expr */ yytestcase(yyruleno==278); + case 279: /* exprx ::= PLUS expr */ yytestcase(yyruleno==279); +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initUnaryOp(yymsp[0].minor.yy490, yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 280: /* exprx ::= expr not_opt BETWEEN expr AND expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initBetween(yymsp[-5].minor.yy490, *(yymsp[-4].minor.yy237), yymsp[-2].minor.yy490, yymsp[0].minor.yy490); + delete yymsp[-4].minor.yy237; + objectForTokens = yygotominor.yy490; + } + break; + case 281: /* exprx ::= expr not_opt IN LP exprlist RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initIn(yymsp[-5].minor.yy490, *(yymsp[-4].minor.yy237), *(yymsp[-1].minor.yy13)); + delete yymsp[-4].minor.yy237; + delete yymsp[-1].minor.yy13; + objectForTokens = yygotominor.yy490; + } + break; + case 282: /* exprx ::= LP select RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initSubSelect(yymsp[-1].minor.yy123); + objectForTokens = yygotominor.yy490; + } + break; + case 283: /* exprx ::= expr not_opt IN LP select RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initIn(yymsp[-5].minor.yy490, *(yymsp[-4].minor.yy237), yymsp[-1].minor.yy123); + delete yymsp[-4].minor.yy237; + objectForTokens = yygotominor.yy490; + } + break; + case 284: /* exprx ::= expr not_opt IN nm dbnm */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initIn(yymsp[-4].minor.yy490, yymsp[-3].minor.yy237, *(yymsp[-1].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-3].minor.yy237; + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 285: /* exprx ::= EXISTS LP select RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initExists(yymsp[-1].minor.yy123); + objectForTokens = yygotominor.yy490; + } + break; + case 286: /* exprx ::= CASE case_operand case_exprlist case_else END */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initCase(yymsp[-3].minor.yy490, *(yymsp[-2].minor.yy13), yymsp[-1].minor.yy490); + delete yymsp[-2].minor.yy13; + objectForTokens = yygotominor.yy490; + } + break; + case 287: /* exprx ::= RAISE LP IGNORE RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initRaise(yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 288: /* exprx ::= RAISE LP raisetype COMMA nm RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initRaise(yymsp[-3].minor.yy0->value, *(yymsp[-1].minor.yy211)); + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 289: /* exprx ::= nm DOT */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[-1].minor.yy211), QString::null, QString::null); + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 290: /* exprx ::= nm DOT nm DOT */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[-3].minor.yy211), *(yymsp[-1].minor.yy211), QString::null); + delete yymsp[-3].minor.yy211; + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 291: /* exprx ::= expr not_opt BETWEEN expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + delete yymsp[-2].minor.yy237; + delete yymsp[-3].minor.yy490; + delete yymsp[0].minor.yy490; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 292: /* exprx ::= CASE case_operand case_exprlist case_else */ +{ + yygotominor.yy490 = new SqliteExpr(); + delete yymsp[-1].minor.yy13; + delete yymsp[-2].minor.yy490; + delete yymsp[0].minor.yy490; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 293: /* exprx ::= expr not_opt IN LP exprlist */ +{ + yygotominor.yy490 = new SqliteExpr(); + delete yymsp[-3].minor.yy237; + delete yymsp[0].minor.yy13; + delete yymsp[-4].minor.yy490; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 294: /* exprx ::= expr not_opt IN ID_DB */ +{ yy_destructor(yypParser,199,&yymsp[-3].minor); +} + break; + case 295: /* exprx ::= expr not_opt IN nm DOT ID_TAB */ + case 296: /* exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN */ yytestcase(yyruleno==296); +{ yy_destructor(yypParser,199,&yymsp[-5].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 298: /* exprx ::= nm DOT nm DOT ID_COL */ +{ yy_destructor(yypParser,177,&yymsp[-4].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 299: /* exprx ::= expr COLLATE ID_COLLATE */ + case 300: /* exprx ::= RAISE LP raisetype COMMA ID_ERR_MSG RP */ yytestcase(yyruleno==300); +{ yy_destructor(yypParser,199,&yymsp[-2].minor); +} + break; + case 302: /* expr ::= */ +{ + yygotominor.yy490 = new SqliteExpr(); + objectForTokens = yygotominor.yy490; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 305: /* likeop ::= LIKE_KW|MATCH */ +{yygotominor.yy374 = new SqliteExpr::LikeOp(SqliteExpr::likeOp(yymsp[0].minor.yy0->value));} + break; + case 306: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ +{ + yymsp[-4].minor.yy13->append(yymsp[-2].minor.yy490); + yymsp[-4].minor.yy13->append(yymsp[0].minor.yy490); + yygotominor.yy13 = yymsp[-4].minor.yy13; + } + break; + case 307: /* case_exprlist ::= WHEN expr THEN expr */ +{ + yygotominor.yy13 = new ParserExprList(); + yygotominor.yy13->append(yymsp[-2].minor.yy490); + yygotominor.yy13->append(yymsp[0].minor.yy490); + } + break; + case 314: /* nexprlist ::= nexprlist COMMA expr */ +{ + yymsp[-2].minor.yy13->append(yymsp[0].minor.yy490); + yygotominor.yy13 = yymsp[-2].minor.yy13; + DONT_INHERIT_TOKENS("nexprlist"); + } + break; + case 315: /* nexprlist ::= exprx */ +{ + yygotominor.yy13 = new ParserExprList(); + yygotominor.yy13->append(yymsp[0].minor.yy490); + } + break; + case 316: /* cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP where_opt */ +{ + yygotominor.yy399 = new SqliteCreateIndex( + *(yymsp[-10].minor.yy237), + *(yymsp[-8].minor.yy237), + *(yymsp[-7].minor.yy211), + *(yymsp[-6].minor.yy211), + *(yymsp[-4].minor.yy211), + *(yymsp[-2].minor.yy139), + yymsp[0].minor.yy490 + ); + delete yymsp[-8].minor.yy237; + delete yymsp[-10].minor.yy237; + delete yymsp[-7].minor.yy211; + delete yymsp[-6].minor.yy211; + delete yymsp[-4].minor.yy211; + delete yymsp[-2].minor.yy139; + objectForTokens = yygotominor.yy399; + } + break; + case 317: /* cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON ID_TAB */ +{ yy_destructor(yypParser,177,&yymsp[-3].minor); +} + break; + case 322: /* idxlist_opt ::= */ +{yygotominor.yy139 = new ParserIndexedColumnList();} + break; + case 323: /* idxlist_opt ::= LP idxlist RP */ +{yygotominor.yy139 = yymsp[-1].minor.yy139;} + break; + case 324: /* idxlist ::= idxlist COMMA idxlist_single */ +{ + yymsp[-2].minor.yy139->append(yymsp[0].minor.yy90); + yygotominor.yy139 = yymsp[-2].minor.yy139; + DONT_INHERIT_TOKENS("idxlist"); + } + break; + case 325: /* idxlist ::= idxlist_single */ +{ + yygotominor.yy139 = new ParserIndexedColumnList(); + yygotominor.yy139->append(yymsp[0].minor.yy90); + } + break; + case 326: /* idxlist_single ::= nm collate sortorder */ + case 327: /* idxlist_single ::= ID_COL */ yytestcase(yyruleno==327); +{ + SqliteIndexedColumn* obj = + new SqliteIndexedColumn( + *(yymsp[-2].minor.yy211), + *(yymsp[-1].minor.yy211), + *(yymsp[0].minor.yy226) + ); + yygotominor.yy90 = obj; + delete yymsp[0].minor.yy226; + delete yymsp[-2].minor.yy211; + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy90; + } + break; + case 331: /* cmd ::= DROP INDEX ifexists fullname */ +{ + yygotominor.yy399 = new SqliteDropIndex(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy66->name1, yymsp[0].minor.yy66->name2); + delete yymsp[-1].minor.yy237; + delete yymsp[0].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 334: /* cmd ::= VACUUM */ +{ + yygotominor.yy399 = new SqliteVacuum(); + objectForTokens = yygotominor.yy399; + } + break; + case 335: /* cmd ::= VACUUM nm */ +{ + yygotominor.yy399 = new SqliteVacuum(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 336: /* cmd ::= PRAGMA nm dbnm */ +{ + yygotominor.yy399 = new SqlitePragma(*(yymsp[-1].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 337: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ + case 339: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ yytestcase(yyruleno==339); +{ + yygotominor.yy399 = new SqlitePragma(*(yymsp[-3].minor.yy211), *(yymsp[-2].minor.yy211), *(yymsp[0].minor.yy21), true); + delete yymsp[-3].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy399; + } + break; + case 338: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ + case 340: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ yytestcase(yyruleno==340); +{ + yygotominor.yy399 = new SqlitePragma(*(yymsp[-4].minor.yy211), *(yymsp[-3].minor.yy211), *(yymsp[-1].minor.yy21), false); + delete yymsp[-4].minor.yy211; + delete yymsp[-3].minor.yy211; + delete yymsp[-1].minor.yy21; + objectForTokens = yygotominor.yy399; + } + break; + case 344: /* nmnum ::= nm */ +{ + yygotominor.yy21 = new QVariant(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + } + break; + case 350: /* minus_num ::= MINUS number */ +{ + if (yymsp[0].minor.yy21->type() == QVariant::Double) + *(yymsp[0].minor.yy21) = -(yymsp[0].minor.yy21->toDouble()); + else if (yymsp[0].minor.yy21->type() == QVariant::LongLong) + *(yymsp[0].minor.yy21) = -(yymsp[0].minor.yy21->toLongLong()); + else + Q_ASSERT_X(true, "producing minus number", "QVariant is neither of Double or LongLong."); + + yygotominor.yy21 = yymsp[0].minor.yy21; + } + break; + case 351: /* number ::= INTEGER */ +{yygotominor.yy21 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toLongLong());} + break; + case 352: /* number ::= FLOAT */ +{yygotominor.yy21 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toDouble());} + break; + case 353: /* cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause BEGIN trigger_cmd_list END */ +{ + yygotominor.yy399 = new SqliteCreateTrigger( + *(yymsp[-13].minor.yy376), + *(yymsp[-11].minor.yy237), + *(yymsp[-10].minor.yy211), + *(yymsp[-9].minor.yy211), + *(yymsp[-5].minor.yy211), + *(yymsp[-8].minor.yy152), + yymsp[-7].minor.yy309, + *(yymsp[-4].minor.yy409), + yymsp[-3].minor.yy490, + *(yymsp[-1].minor.yy214), + 3 + ); + delete yymsp[-11].minor.yy237; + delete yymsp[-13].minor.yy376; + delete yymsp[-8].minor.yy152; + delete yymsp[-4].minor.yy409; + delete yymsp[-10].minor.yy211; + delete yymsp[-5].minor.yy211; + delete yymsp[-9].minor.yy211; + delete yymsp[-1].minor.yy214; + objectForTokens = yygotominor.yy399; + } + break; + case 354: /* cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause */ +{ + QList CL; + + yygotominor.yy399 = new SqliteCreateTrigger( + *(yymsp[-10].minor.yy376), + *(yymsp[-8].minor.yy237), + *(yymsp[-7].minor.yy211), + *(yymsp[-6].minor.yy211), + *(yymsp[-2].minor.yy211), + *(yymsp[-5].minor.yy152), + yymsp[-4].minor.yy309, + *(yymsp[-1].minor.yy409), + yymsp[0].minor.yy490, + CL, + 3 + ); + delete yymsp[-8].minor.yy237; + delete yymsp[-10].minor.yy376; + delete yymsp[-5].minor.yy152; + delete yymsp[-1].minor.yy409; + delete yymsp[-7].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[-6].minor.yy211; + objectForTokens = yygotominor.yy399; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 355: /* cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause BEGIN trigger_cmd_list */ +{ + yygotominor.yy399 = new SqliteCreateTrigger( + *(yymsp[-12].minor.yy376), + *(yymsp[-10].minor.yy237), + *(yymsp[-9].minor.yy211), + *(yymsp[-8].minor.yy211), + *(yymsp[-4].minor.yy211), + *(yymsp[-7].minor.yy152), + yymsp[-6].minor.yy309, + *(yymsp[-3].minor.yy409), + yymsp[-2].minor.yy490, + *(yymsp[0].minor.yy214), + 3 + ); + delete yymsp[-10].minor.yy237; + delete yymsp[-12].minor.yy376; + delete yymsp[-7].minor.yy152; + delete yymsp[-3].minor.yy409; + delete yymsp[-9].minor.yy211; + delete yymsp[-4].minor.yy211; + delete yymsp[-8].minor.yy211; + delete yymsp[0].minor.yy214; + objectForTokens = yygotominor.yy399; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 356: /* cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON ID_TAB */ +{ yy_destructor(yypParser,179,&yymsp[-8].minor); + yy_destructor(yypParser,177,&yymsp[-5].minor); + yy_destructor(yypParser,262,&yymsp[-3].minor); + yy_destructor(yypParser,263,&yymsp[-2].minor); +} + break; + case 359: /* trigger_time ::= BEFORE */ +{yygotominor.yy152 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::BEFORE);} + break; + case 360: /* trigger_time ::= AFTER */ +{yygotominor.yy152 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::AFTER);} + break; + case 361: /* trigger_time ::= INSTEAD OF */ +{yygotominor.yy152 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::INSTEAD_OF);} + break; + case 362: /* trigger_time ::= */ +{yygotominor.yy152 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::null);} + break; + case 363: /* trigger_event ::= DELETE */ +{ + yygotominor.yy309 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::DELETE); + objectForTokens = yygotominor.yy309; + } + break; + case 364: /* trigger_event ::= INSERT */ +{ + yygotominor.yy309 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::INSERT); + objectForTokens = yygotominor.yy309; + } + break; + case 365: /* trigger_event ::= UPDATE */ +{ + yygotominor.yy309 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::UPDATE); + objectForTokens = yygotominor.yy309; + } + break; + case 366: /* trigger_event ::= UPDATE OF inscollist */ +{ + yygotominor.yy309 = new SqliteCreateTrigger::Event(*(yymsp[0].minor.yy445)); + delete yymsp[0].minor.yy445; + objectForTokens = yygotominor.yy309; + } + break; + case 367: /* foreach_clause ::= */ +{yygotominor.yy409 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::null);} + break; + case 368: /* foreach_clause ::= FOR EACH ROW */ +{yygotominor.yy409 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_ROW);} + break; + case 371: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ +{ + yymsp[-2].minor.yy214->append(yymsp[-1].minor.yy399); + yygotominor.yy214 = yymsp[-2].minor.yy214; + DONT_INHERIT_TOKENS("trigger_cmd_list"); + } + break; + case 372: /* trigger_cmd_list ::= trigger_cmd SEMI */ +{ + yygotominor.yy214 = new ParserQueryList(); + yygotominor.yy214->append(yymsp[-1].minor.yy399); + } + break; + case 373: /* trigger_cmd_list ::= SEMI */ +{ + yygotominor.yy214 = new ParserQueryList(); + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 378: /* raisetype ::= ROLLBACK|ABORT|FAIL */ +{yygotominor.yy0 = yymsp[0].minor.yy0;} + break; + case 379: /* cmd ::= DROP TRIGGER ifexists fullname */ +{ + yygotominor.yy399 = new SqliteDropTrigger(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy66->name1, yymsp[0].minor.yy66->name2); + delete yymsp[-1].minor.yy237; + delete yymsp[0].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 382: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ +{ + yygotominor.yy399 = new SqliteAttach(*(yymsp[-4].minor.yy237), yymsp[-3].minor.yy490, yymsp[-1].minor.yy490, yymsp[0].minor.yy490); + delete yymsp[-4].minor.yy237; + objectForTokens = yygotominor.yy399; + } + break; + case 383: /* cmd ::= DETACH database_kw_opt expr */ +{ + yygotominor.yy399 = new SqliteDetach(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy490); + delete yymsp[-1].minor.yy237; + objectForTokens = yygotominor.yy399; + } + break; + case 388: /* cmd ::= REINDEX */ +{yygotominor.yy399 = new SqliteReindex();} + break; + case 389: /* cmd ::= REINDEX nm dbnm */ + case 390: /* cmd ::= REINDEX ID_COLLATE */ yytestcase(yyruleno==390); +{ + yygotominor.yy399 = new SqliteReindex(*(yymsp[-1].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 393: /* cmd ::= ANALYZE */ +{ + yygotominor.yy399 = new SqliteAnalyze(); + objectForTokens = yygotominor.yy399; + } + break; + case 394: /* cmd ::= ANALYZE nm dbnm */ +{ + yygotominor.yy399 = new SqliteAnalyze(*(yymsp[-1].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 397: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ +{ + yygotominor.yy399 = new SqliteAlterTable( + yymsp[-3].minor.yy66->name1, + yymsp[-3].minor.yy66->name2, + *(yymsp[0].minor.yy211) + ); + delete yymsp[0].minor.yy211; + delete yymsp[-3].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 398: /* cmd ::= ALTER TABLE fullname ADD kwcolumn_opt column */ +{ + yygotominor.yy399 = new SqliteAlterTable( + yymsp[-3].minor.yy66->name1, + yymsp[-3].minor.yy66->name2, + *(yymsp[-1].minor.yy237), + yymsp[0].minor.yy425 + ); + delete yymsp[-1].minor.yy237; + delete yymsp[-3].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 399: /* cmd ::= ALTER TABLE fullname RENAME TO ID_TAB_NEW */ +{ yy_destructor(yypParser,181,&yymsp[-3].minor); +} + break; + case 405: /* create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm dbnm USING nm */ +{ + yygotominor.yy399 = new SqliteCreateVirtualTable( + *(yymsp[-4].minor.yy237), + *(yymsp[-3].minor.yy211), + *(yymsp[-2].minor.yy211), + *(yymsp[0].minor.yy211) + ); + delete yymsp[-4].minor.yy237; + delete yymsp[-3].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 406: /* create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm dbnm USING nm LP vtabarglist RP */ +{ + yygotominor.yy399 = new SqliteCreateVirtualTable( + *(yymsp[-7].minor.yy237), + *(yymsp[-6].minor.yy211), + *(yymsp[-5].minor.yy211), + *(yymsp[-3].minor.yy211), + *(yymsp[-1].minor.yy445) + ); + delete yymsp[-6].minor.yy211; + delete yymsp[-5].minor.yy211; + delete yymsp[-3].minor.yy211; + delete yymsp[-7].minor.yy237; + delete yymsp[-1].minor.yy445; + objectForTokens = yygotominor.yy399; + } + break; + case 409: /* vtabarglist ::= vtabarg */ +{ + yygotominor.yy445 = new ParserStringList(); + yygotominor.yy445->append((yymsp[0].minor.yy211)->mid(1)); // mid(1) to skip the first whitespace added in vtabarg + delete yymsp[0].minor.yy211; + } + break; + case 410: /* vtabarglist ::= vtabarglist COMMA vtabarg */ +{ + yymsp[-2].minor.yy445->append((yymsp[0].minor.yy211)->mid(1)); // mid(1) to skip the first whitespace added in vtabarg + yygotominor.yy445 = yymsp[-2].minor.yy445; + delete yymsp[0].minor.yy211; + DONT_INHERIT_TOKENS("vtabarglist"); + } + break; + case 412: /* vtabarg ::= vtabarg vtabargtoken */ +{ + yymsp[-1].minor.yy211->append(" "+ *(yymsp[0].minor.yy211)); + yygotominor.yy211 = yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + } + break; + case 413: /* vtabargtoken ::= ANY */ +{ + yygotominor.yy211 = new QString(yymsp[0].minor.yy0->value); + } + break; + case 414: /* vtabargtoken ::= LP anylist RP */ +{ + yygotominor.yy211 = new QString("("); + yygotominor.yy211->append(*(yymsp[-1].minor.yy211)); + yygotominor.yy211->append(")"); + delete yymsp[-1].minor.yy211; + } + break; + case 416: /* anylist ::= anylist LP anylist RP */ +{ + yygotominor.yy211 = yymsp[-3].minor.yy211; + yygotominor.yy211->append("("); + yygotominor.yy211->append(*(yymsp[-1].minor.yy211)); + yygotominor.yy211->append(")"); + delete yymsp[-1].minor.yy211; + DONT_INHERIT_TOKENS("anylist"); + } + break; + case 417: /* anylist ::= anylist ANY */ +{ + yygotominor.yy211 = yymsp[-1].minor.yy211; + yygotominor.yy211->append(yymsp[0].minor.yy0->value); + DONT_INHERIT_TOKENS("anylist"); + } + break; + case 418: /* with ::= */ +{yygotominor.yy367 = nullptr;} + break; + case 419: /* with ::= WITH wqlist */ +{ + yygotominor.yy367 = yymsp[0].minor.yy367; + objectForTokens = yygotominor.yy367; + } + break; + case 420: /* with ::= WITH RECURSIVE wqlist */ +{ + yygotominor.yy367 = yymsp[0].minor.yy367; + yygotominor.yy367->recursive = true; + objectForTokens = yygotominor.yy367; + } + break; + case 421: /* wqlist ::= nm idxlist_opt AS LP select RP */ +{ + yygotominor.yy367 = SqliteWith::append(*(yymsp[-5].minor.yy211), *(yymsp[-4].minor.yy139), yymsp[-1].minor.yy123); + delete yymsp[-5].minor.yy211; + delete yymsp[-4].minor.yy139; + } + break; + case 422: /* wqlist ::= wqlist COMMA nm idxlist_opt AS LP select RP */ +{ + yygotominor.yy367 = SqliteWith::append(yymsp[-7].minor.yy367, *(yymsp[-5].minor.yy211), *(yymsp[-4].minor.yy139), yymsp[-1].minor.yy123); + delete yymsp[-5].minor.yy211; + delete yymsp[-4].minor.yy139; + DONT_INHERIT_TOKENS("wqlist"); + } + break; + case 423: /* wqlist ::= ID_TAB_NEW */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy367 = new SqliteWith(); + } + break; + default: + /* (0) input ::= cmdlist */ yytestcase(yyruleno==0); + break; + }; + } + assert( yyruleno>=0 && yyruleno<(int)(sizeof(yyRuleInfo)/sizeof(yyRuleInfo[0])) ); + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + + // Store tokens for the rule in parser context + QList allTokens; + QList allTokensWithAllInherited; + QString keyForTokensMap; + int tokensMapKeyCnt; + if (parserContext->setupTokens) + { + if (objectForTokens) + { + // In case this is a list with recurrent references we need + // to clear tokens before adding the new and extended list. + objectForTokens->tokens.clear(); + } + + QList tokens; + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + tokens.clear(); + const char* fieldName = yyTokenName[yypParser->yystack[i].major]; + if (parserContext->isManagedToken(yypParser->yystack[i].minor.yy0)) + tokens += yypParser->yystack[i].minor.yy0; + + tokens += *(yypParser->yystack[i].tokens); + + if (!noTokenInheritanceFields.contains(fieldName)) + { + if (objectForTokens) + { + keyForTokensMap = fieldName; + tokensMapKeyCnt = 2; + while (objectForTokens->tokensMap.contains(keyForTokensMap)) + keyForTokensMap = fieldName + QString::number(tokensMapKeyCnt++); + + objectForTokens->tokensMap[keyForTokensMap] = parserContext->getTokenPtrList(tokens); + } + + allTokens += tokens; + } + else + { + // If field is mentioned only once, then only one occurance of it will be ignored. + // Second one should be inherited. See "anylist" definition for explanation why. + noTokenInheritanceFields.removeOne(fieldName); + } + allTokensWithAllInherited += tokens; + } + if (objectForTokens) + { + objectForTokens->tokens += parserContext->getTokenPtrList(allTokens); + } + } + + // Clear token lists + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + delete yypParser->yystack[i].tokens; + yypParser->yystack[i].tokens = nullptr; + } + + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); + if( yyact < YYNSTATE ){ +#ifdef NDEBUG + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if( yysize ){ + yypParser->yyidx++; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yymsp->minor = yygotominor; + if (parserContext->setupTokens) + *(yypParser->yystack[yypParser->yyidx].tokens) = allTokens; + }else +#endif + { + yy_shift(yypParser,yyact,yygoto,&yygotominor); + if (parserContext->setupTokens) + { + QList* tokensPtr = yypParser->yystack[yypParser->yyidx].tokens; + *tokensPtr = allTokensWithAllInherited + *tokensPtr; + } + } + }else{ + assert( yyact == YYNSTATE + YYNRULE + 1 ); + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + sqlite3_parseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ + sqlite3_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + sqlite3_parseARG_FETCH; +#define TOKEN (yyminor.yy0) + + UNUSED_PARAMETER(yymajor); + parserContext->error(TOKEN, QObject::tr("Syntax error")); + //qDebug() << "near " << TOKEN->toString() << ": syntax error"; + sqlite3_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + sqlite3_parseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ + sqlite3_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "sqlite3_parseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
    +**
  • A pointer to the parser (an opaque structure.) +**
  • The major token number. +**
  • The minor token number. +**
  • An option argument of a grammar-specified type. +**
+** +** Outputs: +** None. +*/ +void sqlite3_parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + sqlite3_parseTOKENTYPE yyminor /* The value for the token */ + sqlite3_parseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ +#if YYSTACKDEPTH<=0 + if( yypParser->yystksz <=0 ){ + /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ + yyminorunion = yyzerominor; + yyStackOverflow(yypParser, &yyminorunion); + return; + } +#endif + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + yypParser->yystack[0].tokens = new QList(); + } + yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + sqlite3_parseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s [%s] (lemon type: %s)\n", + yyTracePrompt, + yyminor->value.toLatin1().data(), + yyminor->typeString().toLatin1().data(), + yyTokenName[yymajor]); } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyactyyerrcnt--; + yymajor = YYNOCODE; + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else{ + assert( yyact == YY_ERROR_ACTION ); +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_reduce_action( + yypParser->yystack[yypParser->yyidx].stateno, + YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yyerrorhit = 1; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.h b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.h new file mode 100644 index 0000000..d18c81c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.h @@ -0,0 +1,167 @@ +#define TK3_ILLEGAL 1 +#define TK3_COMMENT 2 +#define TK3_SPACE 3 +#define TK3_ID 4 +#define TK3_ABORT 5 +#define TK3_ACTION 6 +#define TK3_AFTER 7 +#define TK3_ANALYZE 8 +#define TK3_ASC 9 +#define TK3_ATTACH 10 +#define TK3_BEFORE 11 +#define TK3_BEGIN 12 +#define TK3_BY 13 +#define TK3_CASCADE 14 +#define TK3_CAST 15 +#define TK3_COLUMNKW 16 +#define TK3_CONFLICT 17 +#define TK3_DATABASE 18 +#define TK3_DEFERRED 19 +#define TK3_DESC 20 +#define TK3_DETACH 21 +#define TK3_EACH 22 +#define TK3_END 23 +#define TK3_EXCLUSIVE 24 +#define TK3_EXPLAIN 25 +#define TK3_FAIL 26 +#define TK3_FOR 27 +#define TK3_IGNORE 28 +#define TK3_IMMEDIATE 29 +#define TK3_INDEXED 30 +#define TK3_INITIALLY 31 +#define TK3_INSTEAD 32 +#define TK3_LIKE_KW 33 +#define TK3_MATCH 34 +#define TK3_NO 35 +#define TK3_PLAN 36 +#define TK3_QUERY 37 +#define TK3_KEY 38 +#define TK3_OF 39 +#define TK3_OFFSET 40 +#define TK3_PRAGMA 41 +#define TK3_RAISE 42 +#define TK3_RECURSIVE 43 +#define TK3_RELEASE 44 +#define TK3_REPLACE 45 +#define TK3_RESTRICT 46 +#define TK3_ROW 47 +#define TK3_ROLLBACK 48 +#define TK3_SAVEPOINT 49 +#define TK3_TEMP 50 +#define TK3_TRIGGER 51 +#define TK3_VACUUM 52 +#define TK3_VIEW 53 +#define TK3_VIRTUAL 54 +#define TK3_WITH 55 +#define TK3_WITHOUT 56 +#define TK3_REINDEX 57 +#define TK3_RENAME 58 +#define TK3_CTIME_KW 59 +#define TK3_IF 60 +#define TK3_ANY 61 +#define TK3_OR 62 +#define TK3_AND 63 +#define TK3_NOT 64 +#define TK3_IS 65 +#define TK3_BETWEEN 66 +#define TK3_IN 67 +#define TK3_ISNULL 68 +#define TK3_NOTNULL 69 +#define TK3_NE 70 +#define TK3_EQ 71 +#define TK3_GT 72 +#define TK3_LE 73 +#define TK3_LT 74 +#define TK3_GE 75 +#define TK3_ESCAPE 76 +#define TK3_BITAND 77 +#define TK3_BITOR 78 +#define TK3_LSHIFT 79 +#define TK3_RSHIFT 80 +#define TK3_PLUS 81 +#define TK3_MINUS 82 +#define TK3_STAR 83 +#define TK3_SLASH 84 +#define TK3_REM 85 +#define TK3_CONCAT 86 +#define TK3_COLLATE 87 +#define TK3_BITNOT 88 +#define TK3_SEMI 89 +#define TK3_TRANSACTION 90 +#define TK3_ID_TRANS 91 +#define TK3_COMMIT 92 +#define TK3_TO 93 +#define TK3_CREATE 94 +#define TK3_TABLE 95 +#define TK3_LP 96 +#define TK3_RP 97 +#define TK3_AS 98 +#define TK3_DOT 99 +#define TK3_ID_TAB_NEW 100 +#define TK3_ID_DB 101 +#define TK3_CTX_ROWID_KW 102 +#define TK3_EXISTS 103 +#define TK3_COMMA 104 +#define TK3_ID_COL_NEW 105 +#define TK3_STRING 106 +#define TK3_JOIN_KW 107 +#define TK3_ID_COL_TYPE 108 +#define TK3_CONSTRAINT 109 +#define TK3_DEFAULT 110 +#define TK3_NULL 111 +#define TK3_PRIMARY 112 +#define TK3_UNIQUE 113 +#define TK3_CHECK 114 +#define TK3_REFERENCES 115 +#define TK3_ID_CONSTR 116 +#define TK3_ID_COLLATE 117 +#define TK3_ID_TAB 118 +#define TK3_INTEGER 119 +#define TK3_FLOAT 120 +#define TK3_BLOB 121 +#define TK3_AUTOINCR 122 +#define TK3_ON 123 +#define TK3_INSERT 124 +#define TK3_DELETE 125 +#define TK3_UPDATE 126 +#define TK3_ID_FK_MATCH 127 +#define TK3_SET 128 +#define TK3_DEFERRABLE 129 +#define TK3_FOREIGN 130 +#define TK3_DROP 131 +#define TK3_ID_VIEW_NEW 132 +#define TK3_ID_VIEW 133 +#define TK3_UNION 134 +#define TK3_ALL 135 +#define TK3_EXCEPT 136 +#define TK3_INTERSECT 137 +#define TK3_SELECT 138 +#define TK3_VALUES 139 +#define TK3_DISTINCT 140 +#define TK3_ID_ALIAS 141 +#define TK3_FROM 142 +#define TK3_USING 143 +#define TK3_JOIN 144 +#define TK3_ID_JOIN_OPTS 145 +#define TK3_ID_IDX 146 +#define TK3_ORDER 147 +#define TK3_GROUP 148 +#define TK3_HAVING 149 +#define TK3_LIMIT 150 +#define TK3_WHERE 151 +#define TK3_ID_COL 152 +#define TK3_INTO 153 +#define TK3_VARIABLE 154 +#define TK3_CASE 155 +#define TK3_ID_FN 156 +#define TK3_ID_ERR_MSG 157 +#define TK3_WHEN 158 +#define TK3_THEN 159 +#define TK3_ELSE 160 +#define TK3_INDEX 161 +#define TK3_ID_IDX_NEW 162 +#define TK3_ID_PRAGMA 163 +#define TK3_ID_TRIG_NEW 164 +#define TK3_ID_TRIG 165 +#define TK3_ALTER 166 +#define TK3_ADD 167 diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.y b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.y new file mode 100644 index 0000000..31a66a2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.y @@ -0,0 +1,2406 @@ +%token_prefix TK3_ +%token_type {Token*} +%default_type {Token*} +%extra_argument {ParserContext* parserContext} +%name sqlite3_parse +%start_symbol input + +%syntax_error { + UNUSED_PARAMETER(yymajor); + parserContext->error(TOKEN, QObject::tr("Syntax error")); + //qDebug() << "near " << TOKEN->toString() << ": syntax error"; +} + +%stack_overflow { + UNUSED_PARAMETER(yypMinor); + parserContext->error(QObject::tr("Parser stack overflow")); +} + +%include { +#include "token.h" +#include "parsercontext.h" +#include "parser_helper_stubs.h" +#include "common/utils_sql.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser/ast/sqlitewith.h" +#include +#include + +#define assert(X) Q_ASSERT(X) +#define UNUSED_PARAMETER(X) (void)(X) +#define DONT_INHERIT_TOKENS(X) noTokenInheritanceFields << X +} + +// These are extra tokens used by the lexer but never seen by the +// parser. We put them in a rule so that the parser generator will +// add them to the parse.h output file. + +%nonassoc ILLEGAL COMMENT SPACE. + +// The following directive causes tokens ABORT, AFTER, ASC, etc. to +// fallback to ID if they will not parse as their original value. +// This obviates the need for the "id" nonterminal. +// Those keywords: EXCEPT INTERSECT UNION +// are allowed for fallback if compound selects are disabled, +// which is not this case. +%fallback ID + ABORT ACTION AFTER ANALYZE ASC ATTACH BEFORE BEGIN BY CASCADE CAST COLUMNKW + CONFLICT DATABASE DEFERRED DESC DETACH EACH END EXCLUSIVE EXPLAIN FAIL FOR + IGNORE IMMEDIATE INDEXED INITIALLY INSTEAD LIKE_KW MATCH NO PLAN + QUERY KEY OF OFFSET PRAGMA RAISE RECURSIVE RELEASE REPLACE RESTRICT ROW ROLLBACK + SAVEPOINT TEMP TRIGGER VACUUM VIEW VIRTUAL WITH WITHOUT + REINDEX RENAME CTIME_KW IF + . +%wildcard ANY. + +// Define operator precedence early so that this is the first occurance +// of the operator tokens in the grammer. Keeping the operators together +// causes them to be assigned integer values that are close together, +// which keeps parser tables smaller. +// +// The token values assigned to these symbols is determined by the order +// in which lemon first sees them. It must be the case that ISNULL/NOTNULL, +// NE/EQ, GT/LE, and GE/LT are separated by only a single value. See +// the sqlite3ExprIfFalse() routine for additional information on this +// constraint. +%left OR. +%left AND. +%right NOT. +%left IS MATCH LIKE_KW BETWEEN IN ISNULL NOTNULL NE EQ. +%left GT LE LT GE. +%right ESCAPE. +%left BITAND BITOR LSHIFT RSHIFT. +%left PLUS MINUS. +%left STAR SLASH REM. +%left CONCAT. +%left COLLATE. +%right BITNOT. + +// Input is a single SQL command +%type cmd {SqliteQuery*} +%destructor cmd {delete $$;} + +input ::= cmdlist. + +cmdlist ::= cmdlist ecmd(C). {parserContext->addQuery(C); DONT_INHERIT_TOKENS("cmdlist");} +cmdlist ::= ecmd(C). {parserContext->addQuery(C);} + +%type ecmd {SqliteQuery*} +%destructor ecmd {delete $$;} +ecmd(X) ::= SEMI. {X = new SqliteEmptyQuery();} +ecmd(X) ::= explain(E) cmdx(C) SEMI. { + X = C; + X->explain = E->explain; + X->queryPlan = E->queryPlan; + delete E; + objectForTokens = X; + } + +%type explain {ParserStubExplain*} +%destructor explain {delete $$;} +explain(X) ::= . {X = new ParserStubExplain(false, false);} +explain(X) ::= EXPLAIN. {X = new ParserStubExplain(true, false);} +explain(X) ::= EXPLAIN QUERY PLAN. {X = new ParserStubExplain(true, true);} + +%type cmdx {SqliteQuery*} +%destructor cmdx {delete $$;} +cmdx(X) ::= cmd(C). {X = C;} + +///////////////////// Begin and end transaction. //////////////////////////// + +cmd(X) ::= BEGIN transtype(TT) + trans_opt(TO). { + X = new SqliteBeginTrans( + TT->type, + TO->transactionKw, + TO->name + ); + delete TO; + delete TT; + objectForTokens = X; + } + +%type trans_opt {ParserStubTransDetails*} +%destructor trans_opt {delete $$;} +trans_opt(X) ::= . {X = new ParserStubTransDetails();} +trans_opt(X) ::= TRANSACTION. { + X = new ParserStubTransDetails(); + X->transactionKw = true; + } +trans_opt(X) ::= TRANSACTION nm(N). { + X = new ParserStubTransDetails(); + X->transactionKw = true; + X->name = *(N); + delete N; + } +trans_opt ::= TRANSACTION ID_TRANS. {} + +%type transtype {ParserStubTransDetails*} +%destructor transtype {delete $$;} +transtype(X) ::= . {X = new ParserStubTransDetails();} +transtype(X) ::= DEFERRED. { + X = new ParserStubTransDetails(); + X->type = SqliteBeginTrans::Type::DEFERRED; + } +transtype(X) ::= IMMEDIATE. { + X = new ParserStubTransDetails(); + X->type = SqliteBeginTrans::Type::IMMEDIATE; + } +transtype(X) ::= EXCLUSIVE. { + X = new ParserStubTransDetails(); + X->type = SqliteBeginTrans::Type::EXCLUSIVE; + } +cmd(X) ::= COMMIT trans_opt(T). { + X = new SqliteCommitTrans( + T->transactionKw, + T->name, + false + ); + delete T; + objectForTokens = X; + } +cmd(X) ::= END trans_opt(T). { + X = new SqliteCommitTrans( + T->transactionKw, + T->name, + true + ); + delete T; + objectForTokens = X; + } +cmd(X) ::= ROLLBACK trans_opt(T). { + X = new SqliteRollback( + T->transactionKw, + T->name + ); + delete T; + objectForTokens = X; + } + +%type savepoint_opt {bool*} +%destructor savepoint_opt {delete $$;} +savepoint_opt(X) ::= SAVEPOINT. {X = new bool(true);} +savepoint_opt(X) ::= . {X = new bool(false);} + +cmd(X) ::= SAVEPOINT nm(N). { + X = new SqliteSavepoint(*(N)); + delete N; + objectForTokens = X; + } +cmd(X) ::= RELEASE savepoint_opt(S) nm(N). { + X = new SqliteRelease(*(S), *(N)); + delete N; + objectForTokens = X; + } +cmd(X) ::= ROLLBACK trans_opt(T) TO + savepoint_opt(S) nm(N). { + X = new SqliteRollback( + T->transactionKw, + *(S), + *(N) + ); + delete S; + delete T; + objectForTokens = X; + } +cmd ::= SAVEPOINT ID_TRANS. {} +cmd ::= RELEASE savepoint_opt ID_TRANS. {} +cmd ::= ROLLBACK trans_opt TO savepoint_opt + ID_TRANS. {} + +///////////////////// The CREATE TABLE statement //////////////////////////// + +cmd(X) ::= CREATE temp(T) TABLE + ifnotexists(E) fullname(N) + LP columnlist(CL) + conslist_opt(CS) RP + table_options(F). { + X = new SqliteCreateTable( + *(T), + *(E), + N->name1, + N->name2, + *(CL), + *(CS), + *(F) + ); + delete E; + delete T; + delete CL; + delete CS; + delete N; + delete F; + objectForTokens = X; + } +cmd(X) ::= CREATE temp(T) TABLE + ifnotexists(E) fullname(N) + AS select(S). { + X = new SqliteCreateTable( + *(T), + *(E), + N->name1, + N->name2, + S + ); + delete E; + delete T; + delete N; + objectForTokens = X; + } +cmd ::= CREATE temp TABLE ifnotexists + nm DOT ID_TAB_NEW. {} +cmd ::= CREATE temp TABLE ifnotexists + ID_DB|ID_TAB_NEW. {} + +%type table_options {QString*} +%destructor table_options {delete $$;} +table_options(X) ::= . {X = new QString();} +table_options(X) ::= WITHOUT nm(N). { + if (N->toLower() != "rowid") + parserContext->errorAtToken(QString("Invalid table option: %1").arg(*(N))); + + X = N; + } +table_options ::= WITHOUT CTX_ROWID_KW. {} + +%type ifnotexists {bool*} +%destructor ifnotexists {delete $$;} +ifnotexists(X) ::= . {X = new bool(false);} +ifnotexists(X) ::= IF NOT EXISTS. {X = new bool(true);} + +%type temp {int*} +%destructor temp {delete $$;} +temp(X) ::= TEMP(T). {X = new int( (T->value.length() > 4) ? 2 : 1 );} +temp(X) ::= . {X = new int(0);} + +%type columnlist {ParserCreateTableColumnList*} +%destructor columnlist {delete $$;} +columnlist(X) ::= columnlist(L) + COMMA column(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("columnlist"); + } +columnlist(X) ::= column(C). { + X = new ParserCreateTableColumnList(); + X->append(C); + } + +// A "column" is a complete description of a single column in a +// CREATE TABLE statement. This includes the column name, its +// datatype, and other keywords such as PRIMARY KEY, UNIQUE, REFERENCES, +// NOT NULL and so forth. + +%type column {SqliteCreateTable::Column*} +%destructor column {delete $$;} +column(X) ::= columnid(C) type(T) + carglist(L). { + X = new SqliteCreateTable::Column(*(C), T, *(L)); + delete C; + delete L; + objectForTokens = X; + } + +%type columnid {QString*} +%destructor columnid {delete $$;} +columnid(X) ::= nm(N). {X = N;} +columnid ::= ID_COL_NEW. {} + + +// An IDENTIFIER can be a generic identifier, or one of several +// keywords. Any non-standard keyword can also be an identifier. +%type id {QString*} +%destructor id {delete $$;} +id(X) ::= ID(T). { + X = new QString( + stripObjName( + T->value, + parserContext->dialect + ) + ); + } + +// Why would INDEXED be defined individually like this? I don't know. +// It was like this in the original SQLite grammar, but it doesn't +// make any sense, since we have a fallback mechanism for such things. +// Anyway, this makes "INDEXED" appear in weird places of completion +// suggestions, so I remove it for now. Will see how it works. +// id(X) ::= INDEXED(T). {X = new QString(T->value);} + +// And "ids" is an identifer-or-string. +%type ids {QString*} +%destructor ids {delete $$;} +ids(X) ::= ID|STRING(T). {X = new QString(T->value);} + +// The name of a column or table can be any of the following: +%type nm {QString*} +%destructor nm {delete $$;} +nm(X) ::= id(N). {X = N;} +nm(X) ::= STRING(N). {X = new QString(stripString(N->value));} +nm(X) ::= JOIN_KW(N). {X = new QString(N->value);} + +// A typetoken is really one or more tokens that form a type name such +// as can be found after the column name in a CREATE TABLE statement. +// Multiple tokens are concatenated to form the value of the typetoken. +%type type {SqliteColumnType*} +%destructor type {delete $$;} +type(X) ::= . {X = nullptr;} +type(X) ::= typetoken(T). {X = T;} + +%type typetoken {SqliteColumnType*} +%destructor typetoken {delete $$;} +typetoken(X) ::= typename(N). { + X = new SqliteColumnType(*(N)); + delete N; + objectForTokens = X; + } +typetoken(X) ::= typename(N) + LP signed(P) RP. { + X = new SqliteColumnType(*(N), *(P)); + delete N; + delete P; + objectForTokens = X; + } +typetoken(X) ::= typename(N) LP signed(P) + COMMA signed(S) RP. { + X = new SqliteColumnType(*(N), *(P), *(S)); + delete N; + delete P; + delete S; + objectForTokens = X; + } + +%type typename {QString*} +%destructor typename {delete $$;} +typename(X) ::= ids(I). {X = I;} +typename(X) ::= typename(T) ids(I). { + T->append(" " + *(I)); + delete I; + X = T; + } +typename ::= ID_COL_TYPE. {} + +%type signed {QVariant*} +%destructor signed {delete $$;} +signed(X) ::= plus_num(N). {X = N;} +signed(X) ::= minus_num(N). {X = N;} + +// "carglist" is a list of additional constraints that come after the +// column name and column type in a CREATE TABLE statement. +%type carglist {ParserCreateTableColumnConstraintList*} +%destructor carglist {delete $$;} +carglist(X) ::= carglist(L) ccons(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("carglist"); + } +carglist(X) ::= . {X = new ParserCreateTableColumnConstraintList();} + +%type ccons {SqliteCreateTable::Column::Constraint*} +%destructor ccons {delete $$;} +ccons(X) ::= CONSTRAINT nm(N). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefNameOnly(*(N)); + delete N; + objectForTokens = X; + } +ccons(X) ::= DEFAULT term(T). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefTerm(*(T)); + delete T; + objectForTokens = X; + } +ccons(X) ::= DEFAULT LP expr(E) RP. { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefExpr(E); + objectForTokens = X; + } +ccons(X) ::= DEFAULT PLUS term(T). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefTerm(*(T), false); + delete T; + objectForTokens = X; + } +ccons(X) ::= DEFAULT MINUS term(T). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefTerm(*(T), true); + delete T; + objectForTokens = X; + } +ccons(X) ::= DEFAULT id(I). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefId(*(I)); + delete I; + objectForTokens = X; + } +ccons(X) ::= DEFAULT CTIME_KW(K). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefCTime(K->value); + objectForTokens = X; + } + +// In addition to the type name, we also care about the primary key and +// UNIQUE constraints. +ccons(X) ::= NULL onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initNull(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= NOT NULL onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initNotNull(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= PRIMARY KEY sortorder(O) + onconf(C) autoinc(A). { + X = new SqliteCreateTable::Column::Constraint(); + X->initPk(*(O), *(C), *(A)); + delete O; + delete A; + delete C; + objectForTokens = X; + } +ccons(X) ::= UNIQUE onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initUnique(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= CHECK LP expr(E) RP. { + X = new SqliteCreateTable::Column::Constraint(); + X->initCheck(E); + objectForTokens = X; + } +ccons(X) ::= REFERENCES nm(N) + idxlist_opt(I) refargs(A). { + X = new SqliteCreateTable::Column::Constraint(); + X->initFk(*(N), *(I), *(A)); + delete N; + delete A; + delete I; + objectForTokens = X; + } +ccons(X) ::= defer_subclause(D). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefer(D->initially, D->deferrable); + delete D; + objectForTokens = X; + } +ccons(X) ::= COLLATE ids(I). { + X = new SqliteCreateTable::Column::Constraint(); + X->initColl(*(I)); + delete I; + objectForTokens = X; + } + +ccons ::= CONSTRAINT ID_CONSTR. {} +ccons ::= COLLATE ID_COLLATE. {} +ccons ::= REFERENCES ID_TAB. {} +ccons(X) ::= CHECK LP RP. { + X = new SqliteCreateTable::Column::Constraint(); + X->initCheck(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type term {QVariant*} +%destructor term {delete $$;} +term(X) ::= NULL. { + X = new QVariant(); + } +term(X) ::= INTEGER(N). { + int base = 10; + if (N->value.startsWith("0x", Qt::CaseInsensitive)) + base = 16; + + X = new QVariant(N->value.toLongLong(nullptr, base)); + } +term(X) ::= FLOAT(N). { + X = new QVariant(QVariant(N->value).toDouble()); + } +term(X) ::= STRING|BLOB(S). {X = new QVariant(S->value);} + +// The optional AUTOINCREMENT keyword +%type autoinc {bool*} +%destructor autoinc {delete $$;} +autoinc(X) ::= . {X = new bool(false);} +autoinc(X) ::= AUTOINCR. {X = new bool(true);} + +// The next group of rules parses the arguments to a REFERENCES clause +// that determine if the referential integrity checking is deferred or +// or immediate and which determine what action to take if a ref-integ +// check fails. +%type refargs {ParserFkConditionList*} +%destructor refargs {delete $$;} +refargs(X) ::= . {X = new ParserFkConditionList();} +refargs(X) ::= refargs(L) refarg(A). { + L->append(A); + X = L; + DONT_INHERIT_TOKENS("refargs"); + } + +%type refarg {SqliteForeignKey::Condition*} +%destructor refarg {delete $$;} +refarg(X) ::= MATCH nm(N). { + X = new SqliteForeignKey::Condition(*(N)); + delete N; + } +refarg(X) ::= ON INSERT refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::INSERT, *(R)); delete R;} +refarg(X) ::= ON DELETE refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::DELETE, *(R)); delete R;} +refarg(X) ::= ON UPDATE refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::UPDATE, *(R)); delete R;} +refarg ::= MATCH ID_FK_MATCH. {} + +%type refact {SqliteForeignKey::Condition::Reaction*} +%destructor refact {delete $$;} +refact(X) ::= SET NULL. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_NULL);} +refact(X) ::= SET DEFAULT. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_DEFAULT);} +refact(X) ::= CASCADE. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::CASCADE);} +refact(X) ::= RESTRICT. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::RESTRICT);} +refact(X) ::= NO ACTION. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::NO_ACTION);} + +%type defer_subclause {ParserDeferSubClause*} +%destructor defer_subclause {delete $$;} +defer_subclause(X) ::= NOT DEFERRABLE + init_deferred_pred_opt(I). { + X = new ParserDeferSubClause(SqliteDeferrable::NOT_DEFERRABLE, *(I)); + delete I; + } +defer_subclause(X) ::= DEFERRABLE + init_deferred_pred_opt(I). { + X = new ParserDeferSubClause(SqliteDeferrable::DEFERRABLE, *(I)); + delete I; + } + +%type init_deferred_pred_opt {SqliteInitially*} +%destructor init_deferred_pred_opt {delete $$;} +init_deferred_pred_opt(X) ::= . {X = new SqliteInitially(SqliteInitially::null);} +init_deferred_pred_opt(X) ::= INITIALLY + DEFERRED. {X = new SqliteInitially(SqliteInitially::DEFERRED);} +init_deferred_pred_opt(X) ::= INITIALLY + IMMEDIATE. {X = new SqliteInitially(SqliteInitially::IMMEDIATE);} + +%type conslist_opt {ParserCreateTableConstraintList*} +%destructor conslist_opt {delete $$;} +conslist_opt(X) ::= . {X = new ParserCreateTableConstraintList();} +conslist_opt(X) ::= COMMA conslist(L). {X = L;} + +%type conslist {ParserCreateTableConstraintList*} +%destructor conslist {delete $$;} +conslist(X) ::= conslist(L) tconscomma(CM) + tcons(C). { + C->afterComma = *(CM); + L->append(C); + X = L; + delete CM; + DONT_INHERIT_TOKENS("conslist"); + } +conslist(X) ::= tcons(C). { + X = new ParserCreateTableConstraintList(); + X->append(C); + } + +%type tconscomma {bool*} +%destructor tconscomma {delete $$;} +tconscomma(X) ::= COMMA. {X = new bool(true);} +tconscomma(X) ::= . {X = new bool(false);} + +%type tcons {SqliteCreateTable::Constraint*} +%destructor tcons {delete $$;} +tcons(X) ::= CONSTRAINT nm(N). { + X = new SqliteCreateTable::Constraint(); + X->initNameOnly(*(N)); + delete N; + objectForTokens = X; + } +tcons(X) ::= PRIMARY KEY LP idxlist(L) + autoinc(I) RP onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initPk(*(L), *(I), *(C)); + delete I; + delete C; + delete L; + objectForTokens = X; + } +tcons(X) ::= UNIQUE LP idxlist(L) RP + onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initUnique(*(L), *(C)); + delete C; + delete L; + objectForTokens = X; + } +tcons(X) ::= CHECK LP expr(E) RP onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initCheck(E, *(C)); + objectForTokens = X; + } +tcons(X) ::= FOREIGN KEY LP idxlist(L) RP + REFERENCES nm(N) idxlist_opt(IL) + refargs(R) defer_subclause_opt(D). { + X = new SqliteCreateTable::Constraint(); + X->initFk( + *(L), + *(N), + *(IL), + *(R), + D->initially, + D->deferrable + ); + delete N; + delete R; + delete D; + delete IL; + delete L; + objectForTokens = X; + } + +tcons ::= CONSTRAINT ID_CONSTR. {} +tcons ::= FOREIGN KEY LP idxlist RP + REFERENCES ID_TAB. {} +tcons(X) ::= CHECK LP RP onconf. { + X = new SqliteCreateTable::Constraint(); + X->initCheck(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type defer_subclause_opt {ParserDeferSubClause*} +%destructor defer_subclause_opt {delete $$;} +defer_subclause_opt(X) ::= . {X = new ParserDeferSubClause(SqliteDeferrable::null, SqliteInitially::null);} +defer_subclause_opt(X) ::= + defer_subclause(D). {X = D;} + +// The following is a non-standard extension that allows us to declare the +// default behavior when there is a constraint conflict. + +%type onconf {SqliteConflictAlgo*} +%destructor onconf {delete $$;} +onconf(X) ::= . {X = new SqliteConflictAlgo(SqliteConflictAlgo::null);} +onconf(X) ::= ON CONFLICT resolvetype(R). {X = R;} + +%type orconf {SqliteConflictAlgo*} +%destructor orconf {delete $$;} +orconf(X) ::= . {X = new SqliteConflictAlgo(SqliteConflictAlgo::null);} +orconf(X) ::= OR resolvetype(R). {X = R;} + +%type resolvetype {SqliteConflictAlgo*} +%destructor resolvetype {delete $$;} +resolvetype(X) ::= raisetype(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= IGNORE(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= REPLACE(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} + +////////////////////////// The DROP TABLE ///////////////////////////////////// + +cmd(X) ::= DROP TABLE ifexists(E) + fullname(N). { + X = new SqliteDropTable(*(E), N->name1, N->name2); + delete E; + delete N; + objectForTokens = X; + } + +cmd ::= DROP TABLE ifexists nm DOT + ID_TAB. {} +cmd ::= DROP TABLE ifexists ID_DB|ID_TAB. {} + +%type ifexists {bool*} +%destructor ifexists {delete $$;} +ifexists(X) ::= IF EXISTS. {X = new bool(true);} +ifexists(X) ::= . {X = new bool(false);} + +///////////////////// The CREATE VIEW statement ///////////////////////////// + +cmd(X) ::= CREATE temp(T) VIEW + ifnotexists(E) fullname(N) + AS select(S). { + X = new SqliteCreateView(*(T), *(E), N->name1, N->name2, S); + delete T; + delete E; + delete N; + objectForTokens = X; + } + +cmd ::= CREATE temp VIEW ifnotexists + nm DOT ID_VIEW_NEW. {} +cmd ::= CREATE temp VIEW ifnotexists + ID_DB|ID_VIEW_NEW. {} + +cmd(X) ::= DROP VIEW ifexists(E) + fullname(N). { + X = new SqliteDropView(*(E), N->name1, N->name2); + delete E; + delete N; + objectForTokens = X; + } + +cmd ::= DROP VIEW ifexists nm DOT ID_VIEW. {} +cmd ::= DROP VIEW ifexists ID_DB|ID_VIEW. {} + +//////////////////////// The SELECT statement ///////////////////////////////// + +cmd(X) ::= select_stmt(S). { + X = S; + objectForTokens = X; + } + +%type select_stmt {SqliteQuery*} +%destructor select_stmt {delete $$;} +select_stmt(X) ::= select(S). { + X = S; + // since it's used in trigger: + objectForTokens = X; + } + +%type select {SqliteSelect*} +%destructor select {delete $$;} +select(X) ::= with(W) selectnowith(S). { + X = S; + S->setWith(W); + objectForTokens = X; + } + +%type selectnowith {SqliteSelect*} +%destructor selectnowith {delete $$;} +selectnowith(X) ::= oneselect(S). { + X = SqliteSelect::append(S); + objectForTokens = X; + } +selectnowith(X) ::= selectnowith(S1) + multiselect_op(O) + oneselect(S2). { + X = SqliteSelect::append(S1, *(O), S2); + delete O; + objectForTokens = X; + } +selectnowith(X) ::= values(V). { + X = SqliteSelect::append(*(V)); + delete V; + objectForTokens = X; + } +selectnowith(X) ::= selectnowith(S1) + COMMA + values(V). { + X = SqliteSelect::append(S1, SqliteSelect::CompoundOperator::UNION_ALL, *(V)); + delete V; + objectForTokens = X; + } + +%type multiselect_op {SqliteSelect::CompoundOperator*} +%destructor multiselect_op {delete $$;} +multiselect_op(X) ::= UNION. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION);} +multiselect_op(X) ::= UNION ALL. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION_ALL);} +multiselect_op(X) ::= EXCEPT. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::EXCEPT);} +multiselect_op(X) ::= INTERSECT. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::INTERSECT);} + +%type oneselect {SqliteSelect::Core*} +%destructor oneselect {delete $$;} +oneselect(X) ::= SELECT distinct(D) + selcollist(L) from(F) + where_opt(W) groupby_opt(G) + having_opt(H) orderby_opt(O) + limit_opt(LI). { + X = new SqliteSelect::Core( + *(D), + *(L), + F, + W, + *(G), + H, + *(O), + LI + ); + delete L; + delete D; + delete G; + delete O; + objectForTokens = X; + } + +%type values {ParserExprNestedList*} +%destructor values {delete $$;} +values(X) ::= VALUES LP nexprlist(E) RP. { + X = new ParserExprNestedList(); + X->append(*(E)); + delete E; + } +values(X) ::= values(L) COMMA LP + exprlist(E) RP. { + L->append(*(E)); + X = L; + delete E; + DONT_INHERIT_TOKENS("values"); + } + +%type distinct {int*} +%destructor distinct {delete $$;} +distinct(X) ::= DISTINCT. {X = new int(1);} +distinct(X) ::= ALL. {X = new int(2);} +distinct(X) ::= . {X = new int(0);} + +%type sclp {ParserResultColumnList*} +%destructor sclp {delete $$;} +sclp(X) ::= selcollist(L) COMMA. {X = L;} +sclp(X) ::= . {X = new ParserResultColumnList();} + +%type selcollist {ParserResultColumnList*} +%destructor selcollist {delete $$;} +selcollist(X) ::= sclp(L) expr(E) as(N). { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + E, + N ? N->asKw : false, + N ? N->name : QString::null + ); + + L->append(obj); + X = L; + delete N; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L) STAR. { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn(true); + + L->append(obj); + X = L; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L) nm(N) DOT STAR. { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + true, + *(N) + ); + L->append(obj); + X = L; + delete N; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L). { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = L; + } +selcollist ::= sclp ID_TAB DOT STAR. {} + +// An option "AS " phrase that can follow one of the expressions that +// define the result set, or one of the tables in the FROM clause. + +%type as {ParserStubAlias*} +%destructor as {delete $$;} +as(X) ::= AS nm(N). { + X = new ParserStubAlias(*(N), true); + delete N; + } +as(X) ::= ids(N). { + X = new ParserStubAlias(*(N), false); + delete N; + } +as ::= AS ID_ALIAS. {} +as ::= ID_ALIAS. {} +as(X) ::= . {X = nullptr;} + +// A complete FROM clause. + +%type from {SqliteSelect::Core::JoinSource*} +%destructor from {delete $$;} +from(X) ::= . {X = nullptr;} +from(X) ::= FROM joinsrc(L). {X = L;} + +%type joinsrc {SqliteSelect::Core::JoinSource*} +%destructor joinsrc {delete $$;} +joinsrc(X) ::= singlesrc(S) seltablist(L). { + X = new SqliteSelect::Core::JoinSource( + S, + *(L) + ); + delete L; + objectForTokens = X; + } +joinsrc(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::JoinSource(); + objectForTokens = X; + } + +%type seltablist {ParserOtherSourceList*} +%destructor seltablist {delete $$;} +seltablist(X) ::= seltablist(L) joinop(O) + singlesrc(S) + joinconstr_opt(C). { + SqliteSelect::Core::JoinSourceOther* src = + new SqliteSelect::Core::JoinSourceOther(O, S, C); + + L->append(src); + X = L; + objectForTokens = src; + DONT_INHERIT_TOKENS("seltablist"); + } +seltablist(X) ::= . { + X = new ParserOtherSourceList(); + } + +%type singlesrc {SqliteSelect::Core::SingleSource*} +%destructor singlesrc {delete $$;} +singlesrc(X) ::= nm(N1) dbnm(N2) as(A) + indexed_opt(I). { + X = new SqliteSelect::Core::SingleSource( + *(N1), + *(N2), + A ? A->asKw : false, + A ? A->name : QString::null, + I ? I->notIndexedKw : false, + I ? I->indexedBy : QString::null + ); + delete N1; + delete N2; + delete A; + if (I) + delete I; + objectForTokens = X; + } +singlesrc(X) ::= LP select(S) RP as(A). { + X = new SqliteSelect::Core::SingleSource( + S, + A ? A->asKw : false, + A ? A->name : QString::null + ); + delete A; + objectForTokens = X; + } +singlesrc(X) ::= LP joinsrc(J) RP as(A). { + X = new SqliteSelect::Core::SingleSource( + J, + A ? A->asKw : false, + A ? A->name : QString::null + ); + delete A; + objectForTokens = X; + } +singlesrc(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::SingleSource(); + objectForTokens = X; + } +singlesrc(X) ::= nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::SingleSource(); + X->database = *(N); + delete N; + objectForTokens = X; + } + +singlesrc ::= nm DOT ID_TAB. {} +singlesrc ::= ID_DB|ID_TAB. {} +singlesrc ::= nm DOT ID_VIEW. {} +singlesrc ::= ID_DB|ID_VIEW. {} + +%type joinconstr_opt {SqliteSelect::Core::JoinConstraint*} +%destructor joinconstr_opt {delete $$;} +joinconstr_opt(X) ::= ON expr(E). { + X = new SqliteSelect::Core::JoinConstraint(E); + objectForTokens = X; + } +joinconstr_opt(X) ::= USING LP + inscollist(L) RP. { + X = new SqliteSelect::Core::JoinConstraint(*(L)); + delete L; + objectForTokens = X; + } +joinconstr_opt(X) ::= . {X = nullptr;} + +%type dbnm {QString*} +%destructor dbnm {delete $$;} +dbnm(X) ::= . {X = new QString();} +dbnm(X) ::= DOT nm(N). {X = N;} + +%type fullname {ParserFullName*} +%destructor fullname {delete $$;} +fullname(X) ::= nm(N1) dbnm(N2). { + X = new ParserFullName(); + X->name1 = *(N1); + X->name2 = *(N2); + delete N1; + delete N2; + } + +%type joinop {SqliteSelect::Core::JoinOp*} +%destructor joinop {delete $$;} +joinop(X) ::= COMMA. { + X = new SqliteSelect::Core::JoinOp(true); + objectForTokens = X; + } +joinop(X) ::= JOIN. { + X = new SqliteSelect::Core::JoinOp(false); + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value); + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) nm(N) JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value, *(N)); + delete N; + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) nm(N1) nm(N2) + JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value, *(N1), *(N2)); + delete N1; + delete N1; + objectForTokens = X; + } + +joinop ::= ID_JOIN_OPTS. {} + +// Note that this block abuses the Token type just a little. If there is +// no "INDEXED BY" clause, the returned token is empty (z==0 && n==0). If +// there is an INDEXED BY clause, then the token is populated as per normal, +// with z pointing to the token data and n containing the number of bytes +// in the token. +// +// If there is a "NOT INDEXED" clause, then (z==0 && n==1), which is +// normally illegal. The sqlite3SrcListIndexedBy() function +// recognizes and interprets this as a special case. +%type indexed_opt {ParserIndexedBy*} +%destructor indexed_opt {delete $$;} +indexed_opt(X) ::= . {X = nullptr;} +indexed_opt(X) ::= INDEXED BY nm(N). { + X = new ParserIndexedBy(*(N)); + delete N; + } +indexed_opt(X) ::= NOT INDEXED. {X = new ParserIndexedBy(true);} + +indexed_opt ::= INDEXED BY ID_IDX. {} + +%type orderby_opt {ParserOrderByList*} +%destructor orderby_opt {delete $$;} +orderby_opt(X) ::= . {X = new ParserOrderByList();} +orderby_opt(X) ::= ORDER BY sortlist(L). {X = L;} + +// SQLite3 documentation says it's allowed for "COLLATE name" and expr itself handles this. +%type sortlist {ParserOrderByList*} +%destructor sortlist {delete $$;} +sortlist(X) ::= sortlist(L) COMMA expr(E) + sortorder(O). { + SqliteOrderBy* obj = new SqliteOrderBy(E, *(O)); + L->append(obj); + X = L; + delete O; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sortlist"); + } +sortlist(X) ::= expr(E) sortorder(O). { + SqliteOrderBy* obj = new SqliteOrderBy(E, *(O)); + X = new ParserOrderByList(); + X->append(obj); + delete O; + objectForTokens = obj; + } + +%type sortorder {SqliteSortOrder*} +%destructor sortorder {delete $$;} +sortorder(X) ::= ASC. {X = new SqliteSortOrder(SqliteSortOrder::ASC);} +sortorder(X) ::= DESC. {X = new SqliteSortOrder(SqliteSortOrder::DESC);} +sortorder(X) ::= . {X = new SqliteSortOrder(SqliteSortOrder::null);} + +%type groupby_opt {ParserExprList*} +%destructor groupby_opt {delete $$;} +groupby_opt(X) ::= . {X = new ParserExprList();} +groupby_opt(X) ::= GROUP BY nexprlist(L). {X = L;} +groupby_opt(X) ::= GROUP BY. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserExprList(); + } + +%type having_opt {SqliteExpr*} +%destructor having_opt {delete $$;} +having_opt(X) ::= . {X = nullptr;} +having_opt(X) ::= HAVING expr(E). {X = E;} + +%type limit_opt {SqliteLimit*} +%destructor limit_opt {delete $$;} +limit_opt(X) ::= . {X = nullptr;} +limit_opt(X) ::= LIMIT expr(E). { + X = new SqliteLimit(E); + objectForTokens = X; + } +limit_opt(X) ::= LIMIT expr(E1) OFFSET + expr(E2). { + X = new SqliteLimit(E1, E2, true); + objectForTokens = X; + } +limit_opt(X) ::= LIMIT expr(E1) COMMA + expr(E2). { + X = new SqliteLimit(E1, E2, false); + objectForTokens = X; + } + +/////////////////////////// The DELETE statement ///////////////////////////// + +//%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +//cmd ::= DELETE FROM fullname indexed_opt where_opt +// orderby_opt(O) limit_opt. +//%endif +//%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +cmd(X) ::= delete_stmt(S). { + X = S; + objectForTokens = X; + } + +%type delete_stmt {SqliteQuery*} +%destructor delete_stmt {delete $$;} +delete_stmt(X) ::= with(WI) DELETE FROM + fullname(N) + indexed_opt(I) + where_opt(W). { + if (I) + { + if (!I->indexedBy.isNull()) + { + X = new SqliteDelete( + N->name1, + N->name2, + I->indexedBy, + W, + WI + ); + } + else + { + X = new SqliteDelete( + N->name1, + N->name2, + I->notIndexedKw, + W, + WI + ); + } + delete I; + } + else + { + X = new SqliteDelete( + N->name1, + N->name2, + false, + W, + WI + ); + } + delete N; + // since it's used in trigger: + objectForTokens = X; + } +//%endif + +delete_stmt(X) ::= with(W) DELETE FROM. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->with = W; + X = q; + objectForTokens = X; + } +delete_stmt(X) ::= with(W) DELETE FROM + nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->with = W; + q->database = *(N); + X = q; + objectForTokens = X; + delete N; + } +delete_stmt ::= with DELETE FROM + nm DOT ID_TAB. {} +delete_stmt ::= with DELETE FROM + ID_DB|ID_TAB. {} + +%type where_opt {SqliteExpr*} +%destructor where_opt {delete $$;} +where_opt(X) ::= . {X = nullptr;} +where_opt(X) ::= WHERE expr(E). {X = E;} +where_opt(X) ::= WHERE. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteExpr(); + } + +////////////////////////// The UPDATE command //////////////////////////////// + +//%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +///cmd ::= UPDATE orconf fullname indexed_opt SET setlist where_opt orderby_opt(O) limit_opt. +//%endif +//%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +cmd(X) ::= update_stmt(S). { + X = S; + objectForTokens = X; + } + +%type update_stmt {SqliteQuery*} +%destructor update_stmt {delete $$;} +update_stmt(X) ::= with(WI) UPDATE orconf(C) + fullname(N) indexed_opt(I) SET + setlist(L) where_opt(W). { + X = new SqliteUpdate( + *(C), + N->name1, + N->name2, + I ? I->notIndexedKw : false, + I ? I->indexedBy : QString::null, + *(L), + W, + WI + ); + delete C; + delete N; + delete L; + if (I) + delete I; + // since it's used in trigger: + objectForTokens = X; + } +//%endif + +update_stmt(X) ::= with(WI) UPDATE + orconf(C). { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->with = WI; + X = q; + objectForTokens = X; + delete C; + } +update_stmt(X) ::= with(WI) UPDATE + orconf(C) nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->with = WI; + q->database = *(N); + X = q; + objectForTokens = X; + delete C; + delete N; + } +update_stmt ::= with UPDATE orconf nm DOT + ID_TAB. {} +update_stmt ::= with UPDATE orconf + ID_DB|ID_TAB. {} + +%type setlist {ParserSetValueList*} +%destructor setlist {delete $$;} +setlist(X) ::= setlist(L) COMMA nm(N) EQ + expr(E). { + L->append(ParserSetValue(*(N), E)); + X = L; + delete N; + DONT_INHERIT_TOKENS("setlist"); + } +setlist(X) ::= nm(N) EQ expr(E). { + X = new ParserSetValueList(); + X->append(ParserSetValue(*(N), E)); + delete N; + } +setlist(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserSetValueList(); + } +setlist(X) ::= setlist(L) COMMA. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = L; + } + +setlist ::= setlist COMMA ID_COL. {} +setlist ::= ID_COL. {} + +////////////////////////// The INSERT command ///////////////////////////////// + +cmd(X) ::= insert_stmt(S). { + X = S; + objectForTokens = X; + } + +%type insert_stmt {SqliteQuery*} +%destructor insert_stmt {delete $$;} + +insert_stmt(X) ::= with(W) insert_cmd(C) + INTO fullname(N) + inscollist_opt(I) select(S). { + X = new SqliteInsert( + C->replace, + C->orConflict, + N->name1, + N->name2, + *(I), + S, + W + ); + delete N; + delete C; + delete I; + // since it's used in trigger: + objectForTokens = X; + } +insert_stmt(X) ::= with(W) insert_cmd(C) + INTO fullname(N) + inscollist_opt(I) DEFAULT + VALUES. { + X = new SqliteInsert( + C->replace, + C->orConflict, + N->name1, + N->name2, + *(I), + W + ); + delete N; + delete C; + delete I; + // since it's used in trigger: + objectForTokens = X; + } + +insert_stmt(X) ::= with(W) insert_cmd(C) + INTO. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = C->replace; + q->onConflict = C->orConflict; + q->with = W; + X = q; + objectForTokens = X; + delete C; + } +insert_stmt(X) ::= with(W) insert_cmd(C) + INTO nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = C->replace; + q->onConflict = C->orConflict; + q->with = W; + q->database = *(N); + X = q; + objectForTokens = X; + delete C; + delete N; + } +insert_stmt ::= with insert_cmd INTO + ID_DB|ID_TAB. {} +insert_stmt ::= with insert_cmd INTO + nm DOT ID_TAB. {} + +%type insert_cmd {ParserStubInsertOrReplace*} +%destructor insert_cmd {delete $$;} +insert_cmd(X) ::= INSERT orconf(C). { + X = new ParserStubInsertOrReplace(false, *(C)); + delete C; + } +insert_cmd(X) ::= REPLACE. {X = new ParserStubInsertOrReplace(true);} + +%type inscollist_opt {ParserStringList*} +%destructor inscollist_opt {delete $$;} +inscollist_opt(X) ::= . {X = new ParserStringList();} +inscollist_opt(X) ::= LP inscollist(L) RP. {X = L;} + +%type inscollist {ParserStringList*} +%destructor inscollist {delete $$;} +inscollist(X) ::= inscollist(L) COMMA + nm(N). { + L->append(*(N)); + X = L; + delete N; + DONT_INHERIT_TOKENS("inscollist"); + } +inscollist(X) ::= nm(N). { + X = new ParserStringList(); + X->append(*(N)); + delete N; + } +inscollist(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserStringList(); + } + +inscollist ::= inscollist COMMA ID_COL. {} +inscollist ::= ID_COL. {} + +/////////////////////////// Expression Processing ///////////////////////////// + +%type exprx {SqliteExpr*} +%destructor exprx {delete $$;} +exprx(X) ::= term(T). { + X = new SqliteExpr(); + X->initLiteral(*(T)); + delete T; + objectForTokens = X; + } +exprx(X) ::= CTIME_KW(K). { + X = new SqliteExpr(); + X->initCTime(K->value); + objectForTokens = X; + } +exprx(X) ::= LP expr(E) RP. { + X = new SqliteExpr(); + X->initSubExpr(E); + objectForTokens = X; + } +exprx(X) ::= id(N). { + X = new SqliteExpr(); + X->initId(*(N)); + delete N; + objectForTokens = X; + } +exprx(X) ::= JOIN_KW(N). { + X = new SqliteExpr(); + X->initId(N->value); + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT nm(N2). { + X = new SqliteExpr(); + X->initId(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT nm(N2) DOT nm(N3). { + X = new SqliteExpr(); + X->initId(*(N1), *(N2), *(N3)); + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } +exprx(X) ::= VARIABLE(V). { + X = new SqliteExpr(); + X->initBindParam(V->value); + objectForTokens = X; + } +exprx(X) ::= expr(E) COLLATE ids(I). { + X = new SqliteExpr(); + X->initCollate(E, *(I)); + delete I; + objectForTokens = X; + } +exprx(X) ::= CAST LP expr(E) AS typetoken(T) + RP. { + X = new SqliteExpr(); + X->initCast(E, T); + objectForTokens = X; + } +exprx(X) ::= ID(I) LP distinct(D) + exprlist(L) RP. { + X = new SqliteExpr(); + X->initFunction(I->value, *(D), *(L)); + delete D; + delete L; + objectForTokens = X; + } +exprx(X) ::= ID(I) LP STAR RP. { + X = new SqliteExpr(); + X->initFunction(I->value, true); + objectForTokens = X; + } +exprx(X) ::= expr(E1) AND(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) OR(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) LT|GT|GE|LE(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) EQ|NE(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) + BITAND|BITOR|LSHIFT|RSHIFT(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) PLUS|MINUS(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) STAR|SLASH|REM(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) CONCAT(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) likeop(L) + expr(E2). [LIKE_KW] { + X = new SqliteExpr(); + X->initLike(E1, *(N), *(L), E2); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) likeop(L) + expr(E2) ESCAPE + expr(E3). [LIKE_KW] { + X = new SqliteExpr(); + X->initLike(E1, *(N), *(L), E2, E3); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= expr(E) ISNULL|NOTNULL(N). { + X = new SqliteExpr(); + X->initNull(E, N->value); + objectForTokens = X; + } +exprx(X) ::= expr(E) NOT NULL. { + X = new SqliteExpr(); + X->initNull(E, "NOT NULL"); + objectForTokens = X; + } +exprx(X) ::= expr(E1) IS not_opt(N) + expr(E2). { + X = new SqliteExpr(); + X->initIs(E1, *(N), E2); + delete N; + objectForTokens = X; + } +exprx(X) ::= NOT(O) expr(E). { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + } +exprx(X) ::= BITNOT(O) expr(E). { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= MINUS(O) expr(E). [BITNOT] { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= PLUS(O) expr(E). [BITNOT] { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) BETWEEN + expr(E2) AND + expr(E3). [BETWEEN] { + X = new SqliteExpr(); + X->initBetween(E1, *(N), E2, E3); + delete N; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN LP + exprlist(L) RP. [IN] { + X = new SqliteExpr(); + X->initIn(E, *(N), *(L)); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= LP select(S) RP. { + X = new SqliteExpr(); + X->initSubSelect(S); + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN LP + select(S) RP. [IN] { + X = new SqliteExpr(); + X->initIn(E, *(N), S); + delete N; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN nm(N1) + dbnm(N2). [IN] { + X = new SqliteExpr(); + X->initIn(E, N, *(N1), *(N2)); + delete N; + delete N1; + objectForTokens = X; + } +exprx(X) ::= EXISTS LP select(S) RP. { + X = new SqliteExpr(); + X->initExists(S); + objectForTokens = X; + } +exprx(X) ::= CASE case_operand(O) + case_exprlist(L) + case_else(E) END. { + X = new SqliteExpr(); + X->initCase(O, *(L), E); + delete L; + objectForTokens = X; + } + +exprx(X) ::= RAISE LP IGNORE(R) RP. { + X = new SqliteExpr(); + X->initRaise(R->value); + objectForTokens = X; + } +exprx(X) ::= RAISE LP raisetype(R) COMMA + nm(N) RP. { + X = new SqliteExpr(); + X->initRaise(R->value, *(N)); + delete N; + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT. { + X = new SqliteExpr(); + X->initId(*(N1), QString::null, QString::null); + delete N1; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= nm(N1) DOT nm(N2) DOT. { + X = new SqliteExpr(); + X->initId(*(N1), *(N2), QString::null); + delete N1; + delete N2; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= expr(E1) not_opt(N) BETWEEN + expr(E2). [BETWEEN] { + X = new SqliteExpr(); + delete N; + delete E1; + delete E2; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= CASE case_operand(O) + case_exprlist(L) + case_else(E). { + X = new SqliteExpr(); + delete L; + delete O; + delete E; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= expr(E) not_opt(N) IN LP + exprlist(L). [IN] { + X = new SqliteExpr(); + delete N; + delete L; + delete E; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + +exprx ::= expr not_opt IN ID_DB. [IN] {} +exprx ::= expr not_opt IN nm DOT + ID_TAB. [IN] {} +exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN. {} +exprx ::= nm DOT ID_TAB|ID_COL. {} +exprx ::= nm DOT nm DOT ID_COL. {} +exprx ::= expr COLLATE ID_COLLATE. {} +exprx ::= RAISE LP raisetype COMMA + ID_ERR_MSG RP. {} + +%type expr {SqliteExpr*} +%destructor expr {delete $$;} +expr(X) ::= exprx(E). {X = E;} +expr(X) ::= . { + X = new SqliteExpr(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type not_opt {bool*} +%destructor not_opt {delete $$;} +not_opt(X) ::= . {X = new bool(false);} +not_opt(X) ::= NOT. {X = new bool(true);} + +%type likeop {SqliteExpr::LikeOp*} +%destructor likeop {delete $$;} +likeop(X) ::= LIKE_KW|MATCH(T). {X = new SqliteExpr::LikeOp(SqliteExpr::likeOp(T->value));} + +%type case_exprlist {ParserExprList*} +%destructor case_exprlist {delete $$;} +case_exprlist(X) ::= case_exprlist(L) WHEN + expr(E1) THEN expr(E2). { + L->append(E1); + L->append(E2); + X = L; + } +case_exprlist(X) ::= WHEN expr(E1) THEN + expr(E2). { + X = new ParserExprList(); + X->append(E1); + X->append(E2); + } + +%type case_else {SqliteExpr*} +%destructor case_else {delete $$;} +case_else(X) ::= ELSE expr(E). {X = E;} +case_else(X) ::= . {X = nullptr;} + +%type case_operand {SqliteExpr*} +%destructor case_operand {delete $$;} +case_operand(X) ::= exprx(E). {X = E;} +case_operand(X) ::= . {X = nullptr;} + +%type exprlist {ParserExprList*} +%destructor exprlist {delete $$;} +exprlist(X) ::= nexprlist(L). {X = L;} +exprlist(X) ::= . {X = new ParserExprList();} + +%type nexprlist {ParserExprList*} +%destructor nexprlist {delete $$;} +nexprlist(X) ::= nexprlist(L) COMMA + expr(E). { + L->append(E); + X = L; + DONT_INHERIT_TOKENS("nexprlist"); + } +nexprlist(X) ::= exprx(E). { + X = new ParserExprList(); + X->append(E); + } + +///////////////////////////// The CREATE INDEX command /////////////////////// + +cmd(X) ::= CREATE uniqueflag(U) INDEX + ifnotexists(E) nm(N1) dbnm(N2) + ON nm(N3) LP idxlist(L) RP + where_opt(W). { + X = new SqliteCreateIndex( + *(U), + *(E), + *(N1), + *(N2), + *(N3), + *(L), + W + ); + delete E; + delete U; + delete N1; + delete N2; + delete N3; + delete L; + objectForTokens = X; + } + +cmd ::= CREATE uniqueflag INDEX ifnotexists + nm dbnm ON ID_TAB. {} +cmd ::= CREATE uniqueflag INDEX ifnotexists + nm DOT ID_IDX_NEW. {} +cmd ::= CREATE uniqueflag INDEX ifnotexists + ID_DB|ID_IDX_NEW. {} + + +%type uniqueflag {bool*} +%destructor uniqueflag {delete $$;} +uniqueflag(X) ::= UNIQUE. {X = new bool(true);} +uniqueflag(X) ::= . {X = new bool(false);} + +%type idxlist_opt {ParserIndexedColumnList*} +%destructor idxlist_opt {delete $$;} +idxlist_opt(X) ::= . {X = new ParserIndexedColumnList();} +idxlist_opt(X) ::= LP idxlist(I) RP. {X = I;} + +%type idxlist {ParserIndexedColumnList*} +%destructor idxlist {delete $$;} +idxlist(X) ::= idxlist(L) COMMA + idxlist_single(S). { + L->append(S); + X = L; + DONT_INHERIT_TOKENS("idxlist"); + } +idxlist(X) ::= idxlist_single(S). { + X = new ParserIndexedColumnList(); + X->append(S); + } + +%type idxlist_single {SqliteIndexedColumn*} +%destructor idxlist_single {delete $$;} +idxlist_single(X) ::= nm(N) collate(C) + sortorder(S). { + SqliteIndexedColumn* obj = + new SqliteIndexedColumn( + *(N), + *(C), + *(S) + ); + X = obj; + delete S; + delete N; + delete C; + objectForTokens = X; + } + +idxlist_single ::= ID_COL. {} + +%type collate {QString*} +%destructor collate {delete $$;} +collate(X) ::= . {X = new QString();} +collate(X) ::= COLLATE ids(I). {X = I;} +collate ::= COLLATE ID_COLLATE. {} + + +///////////////////////////// The DROP INDEX command ///////////////////////// + +cmd(X) ::= DROP INDEX ifexists(I) + fullname(N). { + X = new SqliteDropIndex(*(I), N->name1, N->name2); + delete I; + delete N; + objectForTokens = X; + } + +cmd ::= DROP INDEX ifexists nm DOT ID_IDX. {} +cmd ::= DROP INDEX ifexists ID_DB|ID_IDX. {} + +///////////////////////////// The VACUUM command ///////////////////////////// + +cmd(X) ::= VACUUM. { + X = new SqliteVacuum(); + objectForTokens = X; + } +cmd(X) ::= VACUUM nm(N). { + X = new SqliteVacuum(*(N)); + delete N; + objectForTokens = X; + } + +///////////////////////////// The PRAGMA command ///////////////////////////// + +cmd(X) ::= PRAGMA nm(N1) dbnm(N2). { + X = new SqlitePragma(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N1) dbnm(N2) EQ + nmnum(V). { + X = new SqlitePragma(*(N1), *(N2), *(V), true); + delete N1; + delete N2; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N1) dbnm(N2) LP + nmnum(V) RP. { + X = new SqlitePragma(*(N1), *(N2), *(V), false); + delete N1; + delete N2; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N1) dbnm(N2) EQ + minus_num(V). { + X = new SqlitePragma(*(N1), *(N2), *(V), true); + delete N1; + delete N2; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N1) dbnm(N2) LP + minus_num(V) RP. { + X = new SqlitePragma(*(N1), *(N2), *(V), false); + delete N1; + delete N2; + delete V; + objectForTokens = X; + } + +cmd ::= PRAGMA nm DOT ID_PRAGMA. {} +cmd ::= PRAGMA ID_DB|ID_PRAGMA. {} + +%type nmnum {QVariant*} +%destructor nmnum {delete $$;} +nmnum(X) ::= plus_num(N). {X = N;} +nmnum(X) ::= nm(N). { + X = new QVariant(*(N)); + delete N; + } +nmnum(X) ::= ON(T). {X = new QVariant(T->value);} +nmnum(X) ::= DELETE(T). {X = new QVariant(T->value);} +nmnum(X) ::= DEFAULT(T). {X = new QVariant(T->value);} + +%type plus_num {QVariant*} +%destructor plus_num {delete $$;} +plus_num(X) ::= PLUS number(N). {X = N;} +plus_num(X) ::= number(N). {X = N;} + +%type minus_num {QVariant*} +%destructor minus_num {delete $$;} +minus_num(X) ::= MINUS number(N). { + if (N->type() == QVariant::Double) + *(N) = -(N->toDouble()); + else if (N->type() == QVariant::LongLong) + *(N) = -(N->toLongLong()); + else + Q_ASSERT_X(true, "producing minus number", "QVariant is neither of Double or LongLong."); + + X = N; + } + +%type number {QVariant*} +%destructor number {delete $$;} +number(X) ::= INTEGER(N). {X = new QVariant(QVariant(N->value).toLongLong());} +number(X) ::= FLOAT(N). {X = new QVariant(QVariant(N->value).toDouble());} + +//////////////////////////// The CREATE TRIGGER command ///////////////////// + +// Sqlite grammar uses 'fullname' for table name, but it's forbidden anyway, +// because you cannot create trigger for table in different database. +// We use 'nm' instead, as it's more proper. Will see if it truns out to be a problem. +cmd(X) ::= CREATE temp(T) TRIGGER + ifnotexists(IE) nm(N1) dbnm(N2) + trigger_time(TT) trigger_event(EV) + ON nm(N) foreach_clause(FC) + when_clause(WC) BEGIN + trigger_cmd_list(CL) END. { + X = new SqliteCreateTrigger( + *(T), + *(IE), + *(N1), + *(N2), + *(N), + *(TT), + EV, + *(FC), + WC, + *(CL), + 3 + ); + delete IE; + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + delete CL; + objectForTokens = X; + } + +// Support full parsing when no BEGIN and END are present (for completion helper) +cmd(X) ::= CREATE temp(T) TRIGGER + ifnotexists(IE) nm(N1) dbnm(N2) + trigger_time(TT) trigger_event(EV) + ON nm(N) foreach_clause(FC) + when_clause(WC). { + QList CL; + + X = new SqliteCreateTrigger( + *(T), + *(IE), + *(N1), + *(N2), + *(N), + *(TT), + EV, + *(FC), + WC, + CL, + 3 + ); + delete IE; + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +// Support full parsing when no END is present (for completion helper) +cmd(X) ::= CREATE temp(T) TRIGGER + ifnotexists(IE) nm(N1) dbnm(N2) + trigger_time(TT) trigger_event(EV) + ON nm(N) foreach_clause(FC) + when_clause(WC) BEGIN + trigger_cmd_list(CL). { + X = new SqliteCreateTrigger( + *(T), + *(IE), + *(N1), + *(N2), + *(N), + *(TT), + EV, + *(FC), + WC, + *(CL), + 3 + ); + delete IE; + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + delete CL; + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +cmd ::= CREATE temp TRIGGER ifnotexists nm + dbnm trigger_time trigger_event + ON ID_TAB. {} +cmd ::= CREATE temp TRIGGER ifnotexists nm + DOT ID_TRIG_NEW. {} +cmd ::= CREATE temp TRIGGER ifnotexists + ID_DB|ID_TRIG_NEW. {} + +%type trigger_time {SqliteCreateTrigger::Time*} +%destructor trigger_time {delete $$;} +trigger_time(X) ::= BEFORE. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::BEFORE);} +trigger_time(X) ::= AFTER. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::AFTER);} +trigger_time(X) ::= INSTEAD OF. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::INSTEAD_OF);} +trigger_time(X) ::= . {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::null);} + +%type trigger_event {SqliteCreateTrigger::Event*} +%destructor trigger_event {delete $$;} +trigger_event(X) ::= DELETE. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::DELETE); + objectForTokens = X; + } +trigger_event(X) ::= INSERT. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::INSERT); + objectForTokens = X; + } +trigger_event(X) ::= UPDATE. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::UPDATE); + objectForTokens = X; + } +trigger_event(X) ::= UPDATE OF + inscollist(L). { + X = new SqliteCreateTrigger::Event(*(L)); + delete L; + objectForTokens = X; + } + +%type foreach_clause {SqliteCreateTrigger::Scope*} +%destructor foreach_clause {delete $$;} +foreach_clause(X) ::= . {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::null);} +foreach_clause(X) ::= FOR EACH ROW. {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_ROW);} + +%type when_clause {SqliteExpr*} +%destructor when_clause {if ($$) delete $$;} +when_clause(X) ::= . {X = nullptr;} +when_clause(X) ::= WHEN expr(E). {X = E;} + +%type trigger_cmd_list {ParserQueryList*} +%destructor trigger_cmd_list {delete $$;} +trigger_cmd_list(X) ::= trigger_cmd_list(L) + trigger_cmd(C) SEMI. { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("trigger_cmd_list"); + } +trigger_cmd_list(X) ::= trigger_cmd(C) + SEMI. { + X = new ParserQueryList(); + X->append(C); + } +trigger_cmd_list(X) ::= SEMI. { + X = new ParserQueryList(); + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type trigger_cmd {SqliteQuery*} +%destructor trigger_cmd {delete $$;} +trigger_cmd(X) ::= update_stmt(S). {X = S;} +trigger_cmd(X) ::= insert_stmt(S). {X = S;} +trigger_cmd(X) ::= delete_stmt(S). {X = S;} +trigger_cmd(X) ::= select_stmt(S). {X = S;} + +%type raisetype {Token*} +raisetype(X) ::= ROLLBACK|ABORT|FAIL(V). {X = V;} + + +//////////////////////// DROP TRIGGER statement ////////////////////////////// +cmd(X) ::= DROP TRIGGER ifexists(E) + fullname(N). { + X = new SqliteDropTrigger(*(E), N->name1, N->name2); + delete E; + delete N; + objectForTokens = X; + } + +cmd ::= DROP TRIGGER ifexists nm DOT + ID_TRIG. {} +cmd ::= DROP TRIGGER ifexists + ID_DB|ID_TRIG. {} + +//////////////////////// ATTACH DATABASE file AS name ///////////////////////// +cmd(X) ::= ATTACH database_kw_opt(D) + expr(E1) AS expr(E2) key_opt(K). { + X = new SqliteAttach(*(D), E1, E2, K); + delete D; + objectForTokens = X; + } +cmd(X) ::= DETACH database_kw_opt(D) + expr(E). { + X = new SqliteDetach(*(D), E); + delete D; + objectForTokens = X; + } + +%type key_opt {SqliteExpr*} +%destructor key_opt {if ($$) delete $$;} +key_opt(X) ::= . {X = nullptr;} +key_opt(X) ::= KEY expr(E). {X = E;} + +%type database_kw_opt {bool*} +%destructor database_kw_opt {delete $$;} +database_kw_opt(X) ::= DATABASE. {X = new bool(true);} +database_kw_opt(X) ::= . {X = new bool(false);} + +////////////////////////// REINDEX collation ////////////////////////////////// +cmd(X) ::= REINDEX. {X = new SqliteReindex();} +cmd(X) ::= REINDEX nm(N1) dbnm(N2). { + X = new SqliteReindex(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } + +cmd ::= REINDEX ID_COLLATE. {} +cmd ::= REINDEX nm DOT ID_TAB|ID_IDX. {} +cmd ::= REINDEX ID_DB|ID_IDX|ID_TAB. {} + +/////////////////////////////////// ANALYZE /////////////////////////////////// +cmd(X) ::= ANALYZE. { + X = new SqliteAnalyze(); + objectForTokens = X; + } +cmd(X) ::= ANALYZE nm(N1) dbnm(N2). { + X = new SqliteAnalyze(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } + +cmd ::= ANALYZE nm DOT ID_TAB|ID_IDX. {} +cmd ::= ANALYZE ID_DB|ID_IDX|ID_TAB. {} + +//////////////////////// ALTER TABLE table ... //////////////////////////////// +cmd(X) ::= ALTER TABLE fullname(FN) RENAME + TO nm(N). { + X = new SqliteAlterTable( + FN->name1, + FN->name2, + *(N) + ); + delete N; + delete FN; + objectForTokens = X; + } +cmd(X) ::= ALTER TABLE fullname(FN) ADD + kwcolumn_opt(K) column(C). { + X = new SqliteAlterTable( + FN->name1, + FN->name2, + *(K), + C + ); + delete K; + delete FN; + objectForTokens = X; + } + +cmd ::= ALTER TABLE fullname RENAME TO + ID_TAB_NEW. {} +cmd ::= ALTER TABLE nm DOT ID_TAB. {} +cmd ::= ALTER TABLE ID_DB|ID_TAB. {} + +%type kwcolumn_opt {bool*} +%destructor kwcolumn_opt {delete $$;} +kwcolumn_opt(X) ::= . {X = new bool(true);} +kwcolumn_opt(X) ::= COLUMNKW. {X = new bool(false);} + +//////////////////////// CREATE VIRTUAL TABLE ... ///////////////////////////// +cmd(X) ::= create_vtab(C). {X = C;} + +%type create_vtab {SqliteQuery*} +%destructor create_vtab {delete $$;} +create_vtab(X) ::= CREATE VIRTUAL TABLE + ifnotexists(E) nm(N1) + dbnm(N2) USING nm(N3). { + X = new SqliteCreateVirtualTable( + *(E), + *(N1), + *(N2), + *(N3) + ); + delete E; + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } +create_vtab(X) ::= CREATE VIRTUAL TABLE + ifnotexists(E) nm(N1) + dbnm(N2) USING nm(N3) LP + vtabarglist(A) RP. { + X = new SqliteCreateVirtualTable( + *(E), + *(N1), + *(N2), + *(N3), + *(A) + ); + delete N1; + delete N2; + delete N3; + delete E; + delete A; + objectForTokens = X; + } + +create_vtab ::= CREATE VIRTUAL TABLE + ifnotexists nm DOT + ID_TAB_NEW. {} +create_vtab ::= CREATE VIRTUAL TABLE + ifnotexists + ID_DB|ID_TAB_NEW. {} + +%type vtabarglist {ParserStringList*} +%destructor vtabarglist {delete $$;} +vtabarglist(X) ::= vtabarg(A). { + X = new ParserStringList(); + X->append((A)->mid(1)); // mid(1) to skip the first whitespace added in vtabarg + delete A; + } +vtabarglist(X) ::= vtabarglist(L) COMMA + vtabarg(A). { + L->append((A)->mid(1)); // mid(1) to skip the first whitespace added in vtabarg + X = L; + delete A; + DONT_INHERIT_TOKENS("vtabarglist"); + } + +%type vtabarg {QString*} +%destructor vtabarg {delete $$;} +vtabarg(X) ::= . {X = new QString();} +vtabarg(X) ::= vtabarg(A) vtabargtoken(T). { + A->append(" "+ *(T)); + X = A; + delete T; + } + +%type vtabargtoken {QString*} +%destructor vtabargtoken {delete $$;} +vtabargtoken(X) ::= ANY(A). { + X = new QString(A->value); + } +vtabargtoken(X) ::= LP anylist(L) RP. { + X = new QString("("); + X->append(*(L)); + X->append(")"); + delete L; + } + +%type anylist {QString*} +%destructor anylist {delete $$;} +anylist(X) ::= . {X = new QString();} +anylist(X) ::= anylist(L1) LP anylist(L2) + RP. { + X = L1; + X->append("("); + X->append(*(L2)); + X->append(")"); + delete L2; + DONT_INHERIT_TOKENS("anylist"); + } +anylist(X) ::= anylist(L) ANY(A). { + X = L; + X->append(A->value); + DONT_INHERIT_TOKENS("anylist"); + } + +//////////////////////// COMMON TABLE EXPRESSIONS //////////////////////////// +%type with {SqliteWith*} +%type wqlist {SqliteWith*} +%destructor with {delete $$;} +%destructor wqlist {delete $$;} + +with(X) ::= . {X = nullptr;} +with(X) ::= WITH wqlist(W). { + X = W; + objectForTokens = X; + } +with(X) ::= WITH RECURSIVE wqlist(W). { + X = W; + X->recursive = true; + objectForTokens = X; + } + +wqlist(X) ::= nm(N) idxlist_opt(IL) AS + LP select(S) RP. { + X = SqliteWith::append(*(N), *(IL), S); + delete N; + delete IL; + } +wqlist(X) ::= wqlist(WL) COMMA nm(N) + idxlist_opt(IL) AS + LP select(S) RP. { + X = SqliteWith::append(WL, *(N), *(IL), S); + delete N; + delete IL; + DONT_INHERIT_TOKENS("wqlist"); + } +wqlist(X) ::= ID_TAB_NEW. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteWith(); + } diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.cpp new file mode 100644 index 0000000..4f248b0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.cpp @@ -0,0 +1,206 @@ +#include "statementtokenbuilder.h" +#include "parser/ast/sqlitestatement.h" +#include "common/utils_sql.h" +#include + +StatementTokenBuilder& StatementTokenBuilder::withKeyword(const QString& value) +{ + return with(Token::KEYWORD, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withOther(const QString& value) +{ + return with(Token::OTHER, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withOther(const QString& value, Dialect dialect) +{ + return withOther(wrapObjIfNeeded(value, dialect)); +} + +StatementTokenBuilder&StatementTokenBuilder::withStringPossiblyOther(const QString& value, Dialect dialect) +{ + if (value.contains("\"")) + return withOther(wrapObjIfNeeded(value, dialect)); + else + return withOther(wrapObjName(value, NameWrapper::DOUBLE_QUOTE)); +} + +StatementTokenBuilder& StatementTokenBuilder::withOtherList(const QList& value, Dialect dialect, const QString& separator) +{ + bool first = true; + foreach (const QString& str, value) + { + if (!first) + { + if (!separator.isEmpty()) + withOperator(separator); + + withSpace(); + } + withOther(str, dialect); + first = false; + } + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withOtherList(const QList& value, const QString& separator) +{ + bool first = true; + foreach (const QString& str, value) + { + if (!first) + { + if (!separator.isEmpty()) + withOperator(separator); + + withSpace(); + } + withOther(str); + first = false; + } + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withOperator(const QString& value) +{ + return with(Token::OPERATOR, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withComment(const QString& value) +{ + return with(Token::COMMENT, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withFloat(double value) +{ + return with(Token::FLOAT, QString::number(value)); +} + +StatementTokenBuilder& StatementTokenBuilder::withInteger(int value) +{ + return with(Token::INTEGER, QString::number(value)); +} + +StatementTokenBuilder& StatementTokenBuilder::withBindParam(const QString& value) +{ + return with(Token::BIND_PARAM, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withParLeft() +{ + return with(Token::PAR_LEFT, "("); +} + +StatementTokenBuilder& StatementTokenBuilder::withParRight() +{ + return with(Token::PAR_RIGHT, ")"); +} + +StatementTokenBuilder& StatementTokenBuilder::withSpace() +{ + return with(Token::SPACE, " "); +} + +StatementTokenBuilder& StatementTokenBuilder::withBlob(const QString& value) +{ + return with(Token::BLOB, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withString(const QString& value) +{ + return with(Token::STRING, wrapStringIfNeeded(value)); +} + +StatementTokenBuilder& StatementTokenBuilder::withConflict(SqliteConflictAlgo onConflict) +{ + if (onConflict != SqliteConflictAlgo::null) + return withSpace().withKeyword("ON").withSpace().withKeyword("CONFLICT") + .withSpace().withKeyword(sqliteConflictAlgo(onConflict)); + + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withSortOrder(SqliteSortOrder sortOrder) +{ + if (sortOrder != SqliteSortOrder::null) + return withSpace().withKeyword(sqliteSortOrder(sortOrder)); + + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withStatement(SqliteStatement* stmt) +{ + if (!stmt) + return *this; + + stmt->rebuildTokens(); + if (stmt->tokens.size() > 0) + { + if (tokens.size() > 0 && !tokens.last()->isWhitespace() && tokens.last()->type != Token::PAR_LEFT) + withSpace(); + + tokens += stmt->tokens; + tokens.trimRight(Token::OPERATOR, ";"); + } + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withTokens(TokenList tokens) +{ + this->tokens += tokens; + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withLiteralValue(const QVariant& value) +{ + if (value.isNull()) + return *this; + + bool ok; + if (value.userType() == QVariant::Double) + { + value.toDouble(&ok); + if (ok) + { + withFloat(value.toDouble()); + return *this; + } + } + + value.toInt(&ok); + if (ok) + { + withInteger(value.toInt()); + return *this; + } + + QString str = value.toString(); + if (str.startsWith("x'", Qt::CaseInsensitive) && str.endsWith("'")) + { + withBlob(str); + return *this; + } + + withString(str); + return *this; +} + +TokenList StatementTokenBuilder::build() const +{ + return tokens; +} + +void StatementTokenBuilder::clear() +{ + tokens.clear(); + currentIdx = 0; +} + +StatementTokenBuilder& StatementTokenBuilder::with(Token::Type type, const QString& value) +{ + int size = value.size(); + tokens << TokenPtr::create(type, value, currentIdx, currentIdx + size - 1); + currentIdx += size; + return *this; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.h b/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.h new file mode 100644 index 0000000..fcf23be --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.h @@ -0,0 +1,290 @@ +#ifndef STATEMENTTOKENBUILDER_H +#define STATEMENTTOKENBUILDER_H + +#include "token.h" +#include "ast/sqliteconflictalgo.h" +#include "ast/sqlitesortorder.h" +#include "dialect.h" + +class SqliteStatement; + +/** + * @brief Builder producing token list basing on certain inputs. + * + * This builder provides several methods to build list of tokens from various input values. It can produce + * token list for entire AST objects, or it can produce token list for list of names, etc. + * + * Token builder is used in SqliteStatement derived classes to rebuild SqliteStatement::tokens basing on the + * values in their class members. + * + * Typical use case: + * @code + * TokenList SqliteCreateView::rebuildTokensFromContents() + * { + * StatementTokenBuilder builder; + * + * builder.withKeyword("CREATE").withSpace(); + * if (tempKw) + * builder.withKeyword("TEMP").withSpace(); + * else if (temporaryKw) + * builder.withKeyword("TEMPORARY").withSpace(); + * + * builder.withKeyword("VIEW").withSpace(); + * if (ifNotExists) + * builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + * + * if (dialect == Dialect::Sqlite3 && !database.isNull()) + * builder.withOther(database, dialect).withOperator("."); + * + * builder.withOther(view, dialect).withSpace().withKeyword("AS").withStatement(select); + * + * return builder.build(); + * } + * @endcode + */ +class StatementTokenBuilder +{ + public: + /** + * @brief Adds keyword token. + * @param value Value of the keyword token. + * @return Reference to the builder for the further building. + * + * Keyword \p value gets converted to upper case. + */ + StatementTokenBuilder& withKeyword(const QString& value); + + /** + * @brief Adds "other" token (some object name, or other word). + * @param value Value for the token. + * @return Reference to the builder for the further building. + * + * This is used for table names, etc. The \p value is quoted just as passed. + */ + StatementTokenBuilder& withOther(const QString& value); + + /** + * @brief Adds "other" token (some object name, or other word). + * @param value Value for the token. + * @param dialect Dialect used for wrapping the value. + * @return Reference to the builder for the further building. + * + * The \p value is wrapped with the proper wrapper using wrapObjIfNeeded(). + * + * @overload + */ + StatementTokenBuilder& withOther(const QString& value, Dialect dialect); + + /** + * @brief Adds string using double-quote wrapping. + * @param value Value for the token. + * @param dialect Dialect used for wrapping the value if double-quote could not be used. + * @return Reference to the builder for the further building. + * + * The \p value is wrapped with double quote, but if it's not possible then the proper wrapper is used by wrapObjIfNeeded(). + * + * @overload + */ + StatementTokenBuilder& withStringPossiblyOther(const QString& value, Dialect dialect); + + /** + * @brief Adds list of "other" tokens. + * @param value List of values for tokens. + * @param dialect Dialect used for wrapping values. + * @param separator Optional value for separator tokens. + * @return Reference to the builder for the further building. + * + * Given the input \p value, this method produces list of tokens. Additionally it can put extra separator + * token between all produced tokens using the \p separator value. To skip separator tokens pass + * an empty string as the separator value. + */ + StatementTokenBuilder& withOtherList(const QList& value, Dialect dialect, const QString& separator = ","); + + /** + * @brief Adds list of "other" tokens. + * @param value List of values for tokens. + * @param separator Optional value for separator tokens. + * @return Reference to the builder for the further building. + * + * Works just like the other withOtherList() method, except it doesn't wrap values with wrapObjIfNeeded(). + * + * @overload + */ + StatementTokenBuilder& withOtherList(const QList& value, const QString& separator = ","); + + /** + * @brief Adds operator token. + * @param value Value of the operator (";", "+", etc). + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withOperator(const QString& value); + + /** + * @brief Adds comment token. + * @param value Comment value, including start/end characters of the comment. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withComment(const QString& value); + + /** + * @brief Adds decimal number token. + * @param value Value for the token. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withFloat(double value); + + /** + * @brief Add integer numer token. + * @param value Value for the token. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withInteger(int value); + + /** + * @brief Adds bind parameter token. + * @param value Name of the bind parameter, including ":" or "@" at the begining. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withBindParam(const QString& value); + + /** + * @brief Adds left parenthesis token ("("). + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withParLeft(); + + /** + * @brief Adds right parenthesis token (")"). + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withParRight(); + + /** + * @brief Adds a single whitespace token. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withSpace(); + + /** + * @brief Adds BLOB value token. + * @param value BLOB value for the token. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withBlob(const QString& value); + + /** + * @brief Adds string value token. + * @param value Value for the token. + * @return Reference to the builder for the further building. + * + * The string is wrapped with single quote characters if it's not wrapped yet. + */ + StatementTokenBuilder& withString(const QString& value); + + /** + * @brief Adds set of tokens represeting "ON CONFLICT" statement. + * @param onConflict Conflict resolution algorithm to build for. + * @return Reference to the builder for the further building. + * + * If algorithm is SqliteConflictAlgo::null, no tokens are added. + */ + StatementTokenBuilder& withConflict(SqliteConflictAlgo onConflict); + + /** + * @brief Adds space and "ASC"/"DESC" token. + * @param sortOrder Sort order to use. + * @return Reference to the builder for the further building. + * + * If the sort order is SqliteSortOrder::null, no tokens are added. + */ + StatementTokenBuilder& withSortOrder(SqliteSortOrder sortOrder); + + /** + * @brief Adds set of tokens representing entire statement. + * @param stmt Statement to add tokens for. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withStatement(SqliteStatement* stmt); + + /** + * @brief Adds already defined list of tokens to this builder. + * @param tokens Tokens to add. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withTokens(TokenList tokens); + + /** + * @brief Adds literal value token (integer, decimal, string, BLOB). + * @param value Value for the token. + * @return Reference to the builder for the further building. + * + * This method tries to convert given \p value to integer, + * then to double, then it checks if the value has format X'...' + * and if if succeeded at any of those steps, then it adds appropriate + * token. If none of above succeeded, then the string token is added. + */ + StatementTokenBuilder& withLiteralValue(const QVariant& value); + + /** + * @brief Adds tokens representing list of entire statements. + * @param stmtList List of statements to add tokens for. + * @param separator Optional separator to be used for separator tokens. + * @return Reference to the builder for the further building. + * + * This method is very similar to withOtherList(), except it works + * on the entire statements. + */ + template + StatementTokenBuilder& withStatementList(QList stmtList, const QString& separator = ",") + { + bool first = true; + foreach (T* stmt, stmtList) + { + if (!first) + { + if (!separator.isEmpty()) + withOperator(separator); + + withSpace(); + } + withStatement(stmt); + first = false; + } + return *this; + } + + /** + * @brief Provides all tokens added so far as a compat token list. + * @return List of tokens built so far. + */ + TokenList build() const; + + /** + * @brief Cleans up all tokens added so far. + */ + void clear(); + + private: + /** + * @brief Adds token of given type and value. + * @param type Type of the token to add. + * @param value Value for the token to add. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& with(Token::Type type, const QString& value); + + /** + * @brief List of tokens added so far. + */ + TokenList tokens; + + /** + * @brief Current character position index. + * + * This index is used to generate proper values for Token::start and Token::end. + * Each added token increments this index by the value length. + */ + int currentIdx = 0; +}; + +#endif // STATEMENTTOKENBUILDER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/token.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/token.cpp new file mode 100644 index 0000000..5e186dd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/token.cpp @@ -0,0 +1,621 @@ +#include "token.h" +#include "lexer.h" +#include +#include + +Token::Token() + : lemonType(0), type(INVALID), value(QString::null), start(-1), end(-1) +{ +} + +Token::Token(int lemonType, Type type, QString value, qint64 start, qint64 end) + : lemonType(lemonType), type(type), value(value), start(start), end(end) +{} + +Token::Token(int lemonType, Type type, QChar value, qint64 start, qint64 end) + : lemonType(lemonType), type(type), value(value), start(start), end(end) +{} + +Token::Token(int lemonType, Token::Type type, QString value) + : lemonType(lemonType), type(type), value(value), start(-1), end(-1) +{ +} + +Token::Token(QString value) + : lemonType(0), type(INVALID), value(value), start(0), end(0) +{} + +Token::Token(Token::Type type, QString value) + : lemonType(0), type(type), value(value), start(0), end(0) +{ +} + +Token::Token(Token::Type type, QString value, qint64 start, qint64 end) + : lemonType(0), type(type), value(value), start(start), end(end) +{ +} + +Token::~Token() +{ +} + +QString Token::toString() +{ + return "{" + + typeToString(type) + + " " + + value + + " " + + QString::number(start) + + " " + + QString::number(end) + + "}"; +} + +const QString Token::typeToString(Token::Type type) +{ + switch (type) + { + case Token::CTX_ROWID_KW: + return "CTX_ROWID_KW"; + case Token::CTX_NEW_KW: + return "CTX_NEW_KW"; + case Token::CTX_OLD_KW: + return "CTX_OLD_KW"; + case Token::CTX_TABLE_NEW: + return "CTX_TABLE_NEW"; + case Token::CTX_INDEX_NEW: + return "CTX_INDEX_NEW"; + case Token::CTX_VIEW_NEW: + return "CTX_VIEW_NEW"; + case Token::CTX_TRIGGER_NEW: + return "CTX_TRIGGER_NEW"; + case Token::CTX_ALIAS: + return "CTX_ALIAS"; + case Token::CTX_TRANSACTION: + return "CTX_transaction"; + case Token::CTX_COLUMN_NEW: + return "CTX_COLUMN_NEW"; + case Token::CTX_COLUMN_TYPE: + return "CTX_COLUMN_TYPE"; + case Token::CTX_CONSTRAINT: + return "CTX_CONSTRAINT"; + case Token::CTX_FK_MATCH: + return "CTX_FK_MATCH"; + case Token::CTX_PRAGMA: + return "CTX_PRAGMA"; + case Token::CTX_ERROR_MESSAGE: + return "CTX_ERROR_MESSAGE"; + case Token::CTX_COLUMN: + return "CTX_COLUMN"; + case Token::CTX_TABLE: + return "CTX_TABLE"; + case Token::CTX_DATABASE: + return "CTX_DATABASE"; + case Token::CTX_FUNCTION: + return "CTX_FUNCTION"; + case Token::CTX_COLLATION: + return "CTX_COLLATION"; + case Token::CTX_INDEX: + return "CTX_INDEX"; + case Token::CTX_TRIGGER: + return "CTX_TRIGGER"; + case Token::CTX_VIEW: + return "CTX_VIEW"; + case Token::CTX_JOIN_OPTS: + return "CTX_JOIN_OPTS"; + case Token::INVALID: + return "INVALID"; + case Token::OTHER: + return "OTHER"; + case Token::STRING: + return "STRING"; + case Token::COMMENT: + return "COMMENT"; + case Token::FLOAT: + return "FLOAT"; + case Token::INTEGER: + return "INTEGER"; + case Token::BIND_PARAM: + return "BIND_PARAM"; + case Token::OPERATOR: + return "OPERATOR"; + case Token::PAR_LEFT: + return "PAR_LEFT"; + case Token::PAR_RIGHT: + return "PAR_RIGHT"; + case Token::SPACE: + return "SPACE"; + case Token::BLOB: + return "BLOB"; + case Token::KEYWORD: + return "KEYWORD"; + } + + return ""; +} + +Range Token::getRange() +{ + return Range(start, end); +} + +bool Token::isWhitespace() const +{ + return (type == SPACE || type == COMMENT); +} + +bool Token::isSeparating() const +{ + switch (type) + { + case Token::SPACE: + case Token::PAR_LEFT: + case Token::PAR_RIGHT: + case Token::OPERATOR: + return true; + default: + break; + } + return false; +} + +bool Token::isDbObjectType() const +{ + return ((type & TOKEN_TYPE_MASK_DB_OBJECT) == TOKEN_TYPE_MASK_DB_OBJECT); +} + +QString Token::typeString() const +{ + return typeToString(type); +} + +int Token::operator ==(const Token &other) +{ + return type == other.type && value == other.value && start == other.start && end == other.end; +} + +bool Token::operator <(const Token &other) const +{ + if (start == other.start) + return end < other.end; + else + return start < other.start; +} + +uint qHash(const TokenPtr& token) +{ + // This doesn't look nice, but it's good enough to satisfy a hash table. + // It's fast and quite distinguishable. + // It's rare to have two pointers with the same int type representation, + // and if that happens, there's always a comparision operator. + return (uint)reinterpret_cast(token.data()); +} + +TokenList::TokenList() + : QList() +{ +} + +TokenList::TokenList(const QList& other) + : QList(other) +{ +} + +QString TokenList::toString() const +{ + return toStringList().join(" "); +} + +QStringList TokenList::toStringList() const +{ + QStringList strList; + TokenPtr t; + foreach (t, *this) + strList << t->toString(); + + return strList; +} + +int TokenList::indexOf(TokenPtr token) const +{ + return QList::indexOf(token); +} + +int TokenList::indexOf(Token::Type type) const +{ + int i; + findFirst(type, &i); + return i; +} + +int TokenList::indexOf(Token::Type type, const QString &value, Qt::CaseSensitivity caseSensitivity) const +{ + int i; + findFirst(type, value, caseSensitivity, &i); + return i; +} + +int TokenList::indexOf(const QString &value, Qt::CaseSensitivity caseSensitivity) const +{ + int i; + findFirst(value, caseSensitivity, &i); + return i; +} + +int TokenList::lastIndexOf(TokenPtr token) const +{ + return QList::lastIndexOf(token); +} + +int TokenList::lastIndexOf(Token::Type type) const +{ + int i; + findLast(type, &i); + return i; +} + +int TokenList::lastIndexOf(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity) const +{ + int i; + findLast(type, value, caseSensitivity, &i); + return i; +} + +int TokenList::lastIndexOf(const QString& value, Qt::CaseSensitivity caseSensitivity) const +{ + int i; + findLast(value, caseSensitivity, &i); + return i; +} + +TokenPtr TokenList::find(Token::Type type) const +{ + return findFirst(type, nullptr); +} + +TokenPtr TokenList::find(Token::Type type, const QString &value, Qt::CaseSensitivity caseSensitivity) const +{ + return findFirst(type, value, caseSensitivity, nullptr); +} + +TokenPtr TokenList::find(const QString &value, Qt::CaseSensitivity caseSensitivity) const +{ + return findFirst(value, caseSensitivity, nullptr); +} + +TokenPtr TokenList::findLast(Token::Type type) const +{ + return findLast(type, nullptr); +} + +TokenPtr TokenList::findLast(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity) const +{ + return findLast(type, value, caseSensitivity, nullptr); +} + +TokenPtr TokenList::findLast(const QString& value, Qt::CaseSensitivity caseSensitivity) const +{ + return findLast(value, caseSensitivity, nullptr); +} + +TokenPtr TokenList::atCursorPosition(quint64 cursorPosition) const +{ + foreach (TokenPtr token, *this) + { + if (token->getRange().contains(cursorPosition)) + return token; + } + return TokenPtr(); +} + +void TokenList::insert(int i, const TokenList &list) +{ + foreach (TokenPtr token, list) + QList::insert(i++, token); +} + +void TokenList::insert(int i, TokenPtr token) +{ + QList::insert(i, token); +} + +TokenList &TokenList::operator =(const QList &other) +{ + QList::operator =(other); + return *this; +} + +QString TokenList::detokenize() const +{ + return Lexer::detokenize(*this); +} + +void TokenList::replace(int startIdx, int length, const TokenList& newTokens) +{ + for (int i = 0; i < length; i++) + removeAt(startIdx); + + insert(startIdx, newTokens); +} + +void TokenList::replace(int startIdx, int length, TokenPtr newToken) +{ + for (int i = 0; i < length; i++) + removeAt(startIdx); + + insert(startIdx, newToken); +} + +void TokenList::replace(int startIdx, TokenPtr newToken) +{ + QList::replace(startIdx, newToken); +} + +void TokenList::replace(int startIdx, const TokenList& newTokens) +{ + replace(startIdx, 1, newTokens); +} + +int TokenList::replace(TokenPtr startToken, TokenPtr endToken, const TokenList& newTokens) +{ + int startIdx = indexOf(startToken); + if (startIdx < 0) + return 0; + + int endIdx = indexOf(endToken); + if (endIdx < 0) + return 0; + + replace(startIdx, endIdx - startIdx, newTokens); + return endIdx - startIdx; +} + +int TokenList::replace(TokenPtr startToken, TokenPtr endToken, TokenPtr newToken) +{ + int startIdx = indexOf(startToken); + if (startIdx < 0) + return 0; + + int endIdx = indexOf(endToken); + if (endIdx < 0) + return 0; + + replace(startIdx, endIdx - startIdx, newToken); + return endIdx - startIdx; +} + +bool TokenList::replace(TokenPtr oldToken, TokenPtr newToken) +{ + int idx = indexOf(oldToken); + if (idx < 0) + return false; + + replace(idx, newToken); + return true; +} + +bool TokenList::replace(TokenPtr oldToken, const TokenList& newTokens) +{ + int idx = indexOf(oldToken); + if (idx < 0) + return false; + + replace(idx, newTokens); + return true; +} + +bool TokenList::remove(TokenPtr startToken, TokenPtr endToken) +{ + int startIdx = indexOf(startToken); + if (startIdx < 0) + return false; + + int endIdx = indexOf(endToken); + if (endIdx < 0) + return false; + + if (endIdx < startIdx) + return false; + + for (int i = startIdx; i < endIdx; i++) + removeAt(startIdx); + + return true; +} + +bool TokenList::remove(Token::Type type) +{ + int idx = indexOf(type); + if (idx == -1) + return false; + + removeAt(idx); + return true; +} + +void TokenList::trimLeft() +{ + while (size() > 0 && first()->isWhitespace()) + removeFirst(); +} + +void TokenList::trimRight() +{ + while (size() > 0 && last()->isWhitespace()) + removeLast(); +} + +void TokenList::trim() +{ + trimLeft(); + trimRight(); +} + +void TokenList::trimLeft(Token::Type type, const QString& alsoTrim) +{ + while (size() > 0 && (first()->isWhitespace() || (first()->type == type && first()->value == alsoTrim))) + removeFirst(); +} + +void TokenList::trimRight(Token::Type type, const QString& alsoTrim) +{ + while (size() > 0 && (last()->isWhitespace() || (last()->type == type && last()->value == alsoTrim))) + removeLast(); +} + +void TokenList::trim(Token::Type type, const QString& alsoTrim) +{ + trimLeft(type, alsoTrim); + trimRight(type, alsoTrim); +} + +TokenList TokenList::filter(Token::Type type) const +{ + TokenList filtered; + foreach (TokenPtr token, *this) + if (token->type == type) + filtered << token; + + return filtered; +} + +TokenList TokenList::filterWhiteSpaces() const +{ + TokenList filtered; + foreach (TokenPtr token, *this) + if (!token->isWhitespace()) + filtered << token; + + return filtered; +} + +TokenList TokenList::mid(int pos, int length) const +{ + TokenList newList = QList::mid(pos, length); + return newList; +} + +TokenPtr TokenList::findFirst(Token::Type type, int *idx) const +{ + int i = -1; + TokenPtr token; + QListIterator it(*this); + while (it.hasNext()) + { + token = it.next(); + i++; + if (token->type == type) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + +TokenPtr TokenList::findFirst(Token::Type type, const QString &value, Qt::CaseSensitivity caseSensitivity, int *idx) const +{ + int i = -1; + TokenPtr token; + QListIterator it(*this); + while (it.hasNext()) + { + token = it.next(); + i++; + if (token->type != type) + continue; + + if (token->value.compare(value, caseSensitivity) == 0) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + +TokenPtr TokenList::findFirst(const QString &value, Qt::CaseSensitivity caseSensitivity, int *idx) const +{ + int i = -1; + TokenPtr token; + QListIterator it(*this); + while (it.hasNext()) + { + token = it.next(); + i++; + if (token->value.compare(value, caseSensitivity) == 0) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + + +TokenPtr TokenList::findLast(Token::Type type, int* idx) const +{ + int i = size(); + TokenPtr token; + QListIterator it(*this); + it.toBack(); + while (it.hasPrevious()) + { + token = it.previous(); + i--; + if (token->type == type) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + +TokenPtr TokenList::findLast(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const +{ + int i = size(); + TokenPtr token; + QListIterator it(*this); + it.toBack(); + while (it.hasPrevious()) + { + token = it.previous(); + i--; + if (token->type != type) + continue; + + if (token->value.compare(value, caseSensitivity) == 0) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + +TokenPtr TokenList::findLast(const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const +{ + int i = size(); + TokenPtr token; + QListIterator it(*this); + it.toBack(); + while (it.hasPrevious()) + { + token = it.previous(); + i--; + if (token->value.compare(value, caseSensitivity) == 0) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/token.h b/SQLiteStudio3/coreSQLiteStudio/parser/token.h new file mode 100644 index 0000000..1090bd4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/token.h @@ -0,0 +1,733 @@ +#ifndef TOKEN_H +#define TOKEN_H + +#include "common/utils.h" +#include +#include +#include + +/** @file */ + +/** + * @def TOKEN_TYPE_MASK_DB_OBJECT + * + * Bit mask used to test Token::Type for representing any database object name, in any form. + * It's used in Token::isDbObjectType(). + */ +#define TOKEN_TYPE_MASK_DB_OBJECT 0x1000 + +struct Token; + +/** + * @brief Shared pointer to the Token. + */ +typedef QSharedPointer TokenPtr; + +/** + * @brief SQL query entity representing isolated part of the query. + * + * Tokens are generated by Lexer. Each token represents isolated part of the query, + * like a name, a number, an operator, a string, a keyword, or a comment, etc. + * + * In other words, tokenizing SQL query is splitting it into logical parts. + * + * Each token has a type, a value and position indexes of where it starts and where it ends (in the query string). + * + * Tokens are used mainly by the Parser to perform syntax analysis, but they can be also very helpful + * in other areas. They provide easy way for safe manipulation on the query string, without worrying + * about counting open or close characters of the string, etc. If the string has a single-quote used twice inside, + * this is still a regular SQL string and in that case there will be only a single string token representing it. + * + * If you're constructing Token outside of the Lemon parser, you should be interested only in 4 constructors: + * Token(), Token(QString value), Token(Type type, QString value) + * and Token(Type type, QString value, qint64 start, qint64 end). + * Rest of the constructors are to be used from the Lemon parser, as they require Lemon token ID to be provided. + * + * You will usually have the most to do with tokens when dealing with SqliteStatement and its 2 members: + * SqliteStatement::tokens and SqliteStatement::tokenMap. + */ +struct API_EXPORT Token +{ + /** + * @brief Token type. + * + * There are 2 kind of types - regular and context-oriented. + * + * Regular types are those defined by the SQL grammar and they represent real tokens. A special token type + * from group of regular types is the Type::INVALID, which means, that the character(s) encountered + * by the Lexer are invalid in SQL syntax understanding, or when there was no more query characters to read + * (which in this case means that tokenizing ended before this token). + * + * The context-oriented types are special meta types used by the Parser to probe potential candidates + * for next valid token when Parser::getNextTokenCandidates() is called. They are then processed by + * CompletionHelper. You won't deal with this kind of token types on regular basis. Context-oriented + * types are those starting with CTX_. + */ + enum Type + { + INVALID = 0x0001, /**< Invalid token, or no more tokens available from Lexer. */ + OTHER = 0x1002, /**< A name, a word. */ + STRING = 0x0003, /**< A string (value will be stripped of the surrounding quotes). */ + COMMENT = 0x0004, /**< A comment, including starting/ending markers. */ + FLOAT = 0x0005, /**< A decimal number. */ + INTEGER = 0x0006, /**< An integer number. */ + BIND_PARAM = 0x0007, /**< A bind parameter (:param, \@param, or ?). */ + OPERATOR = 0x0008, /**< An operator (like ";", "+", ",", etc). */ + PAR_LEFT = 0x0009, /**< A left parenthesis ("("). */ + PAR_RIGHT = 0x0010, /**< A right parenthesis (")"). */ + SPACE = 0x0011, /**< White space(s), including new line characters and tabs. */ + BLOB = 0x0012, /**< Literal BLOB value (X'...' or x'...'). */ + KEYWORD = 0x0013, /**< A keyword (see getKeywords3() and getKeywords2()). */ + CTX_COLUMN = 0x1014, /**< Existing column name is valid at this token position. */ + CTX_TABLE = 0x1015, /**< Existing table name is valid at this token potision. */ + CTX_DATABASE = 0x1016, /**< Database name is valid at this token position. */ + CTX_FUNCTION = 0x0017, /**< Function name is valid at this token position. */ + CTX_COLLATION = 0x0018, /**< Collation name is valid at this token position. */ + CTX_INDEX = 0x1019, /**< Existing index name is valid at this token position. */ + CTX_TRIGGER = 0x1020, /**< Existing trigger name is valid at this token position. */ + CTX_VIEW = 0x1021, /**< View name is valid at this token position. */ + CTX_JOIN_OPTS = 0x0022, /**< JOIN keywords are valid at this token position (see getJoinKeywords()). */ + CTX_TABLE_NEW = 0x0023, /**< Name for new table is valid at this token position. */ + CTX_INDEX_NEW = 0x0024, /**< Name for new index is valid at this token position. */ + CTX_VIEW_NEW = 0x0025, /**< Name for new view is valid at this token position. */ + CTX_TRIGGER_NEW = 0x0026, /**< Name for new trigger is valid at this token position. */ + CTX_ALIAS = 0x0027, /**< Alias name is valid at this token position. */ + CTX_TRANSACTION = 0x0028, /**< Transaction name is valid at this token position. */ + CTX_COLUMN_NEW = 0x0029, /**< Name for the new column is valid at this token position. */ + CTX_COLUMN_TYPE = 0x0030, /**< Data type for the new column is valid at this token position. */ + CTX_CONSTRAINT = 0x0031, /**< Name for the new constraint is valid at this token position. */ + CTX_FK_MATCH = 0x0032, /**< MATCH keywords are valid at this token position (see getFkMatchKeywords()). */ + CTX_PRAGMA = 0x0033, /**< Pragma name is valid at this token position. */ + CTX_ROWID_KW = 0x0034, /**< ROWID keywords is valid at this token position (see isRowIdKeyword()). */ + CTX_NEW_KW = 0x0035, /**< The NEW keyword is valid at this token position. */ + CTX_OLD_KW = 0x0036, /**< The OLD keyword is valid at this token position. */ + CTX_ERROR_MESSAGE = 0x0037 /**< Error message string is valid at this token position. */ + }; + + /** + * @brief Creates empty token with type Type::INVALID. + * + * Lemon token ID is set to 0 and start/end positions are set to -1. + */ + Token(); + + /** + * @brief Creates fully defined token. + * @param lemonType Lemon token ID to use (see sqlite2_parser.h and sqlite3_parser.h). + * @param type Token type. + * @param value Value of the token. + * @param start Start position of the token (index of the first character in the query). + * @param end End position of the token (index of last character in the query). + * + * This constructor is intended to be used from Lemon parser only. For other use cases + * use constructors without the \p lemonType parameter, unless you need it and you know what you're doing. + */ + Token(int lemonType, Type type, QString value, qint64 start, qint64 end); + + /** + * @overload + */ + Token(int lemonType, Type type, QChar value, qint64 start, qint64 end); + + /** + * @overload + */ + Token(int lemonType, Type type, QString value); + + /** + * @brief Creates token with type Type::INVALID and given value. + * @param value Value for the token. + * + * Start/end positions are set to -1. + */ + explicit Token(QString value); + + /** + * @brief Creates token of given type and with given value. + * @param type Type for the token. + * @param value Value for the token. + * + * Start/end positions are set to -1. + */ + Token(Type type, QString value); + + /** + * @brief Creates fully defined token. + * @param type Type of the token. + * @param value Value for the token. + * @param start Start position of the token (index of the first character in the query). + * @param end End position of the token (index of last character in the query). + */ + Token(Type type, QString value, qint64 start, qint64 end); + + /** + * @brief Destructor declared as virtual. Does nothing in this implementation. + */ + virtual ~Token(); + + /** + * @brief Serializes token to human readable form. + * @return Token values in format: {type value start end} + */ + QString toString(); + + /** + * @brief Converts given token type into its string representation. + * @param type Type to convert. + * @return Type as a string (same as textual representation of the enum in the code). + */ + static const QString typeToString(Type type); + + /** + * @brief Provides range of the token in the query. + * @return Start and end character index in relation to the query it comes from. + */ + Range getRange(); + + /** + * @brief Tests whether this token represents any kind of whitespace. + * @return true if it's a whitespace, or false otherwise. + * + * Note, that from SQL perspective also comments are whitespaces. + */ + bool isWhitespace() const; + + /** + * @brief Tests whether this token represents separating value (like an operator, or parenthesis) in SQL understanding. + * @return true if it's a separating token, or false otherwise. + */ + bool isSeparating() const; + + /** + * @brief Tests whether this token is representing any kind of database object name. + * @return true if the token is the name an object, or false otherwise. + * + * From regular token types only the Type::OTHER represents. + * For context-oriented types there are several types representing object name. + * Use this method to find out which is and which is not. + * + * You won't need to use this method in most cases. It's useful only to CompletionHelper + * for now. + */ + bool isDbObjectType() const; + + /** + * @brief Converts token's type into a string representation. + * @return Token type as a string. + */ + QString typeString() const; + + /** + * @brief Compares other token to this token. + * @param other Other token to compare. + * @return 1 if tokens are equal, 0 if they're different. + * + * Tokens are equal then 4 members are equal: type, value, start and end. + * The lemonType member is ignored by this operator. + */ + int operator==(const Token& other); + + /** + * @brief Compares other token to this token and tells which one is greater. + * @param other Other token to compare. + * @return true if the other token is greater than this one, or false if it's smaller or equal. + * + * This operator compares only 2 members: the start and the end indexes. This operator + * is used to sort tokens by the character position they occurred at. + * + * The start value has higher precedence than the end value, but if start values are equal, + * then the end value is conclusive. + */ + bool operator<(const Token& other) const; + + /** + * @brief Lemon token ID. Used by the Parser class only. + */ + int lemonType; + + /** + * @brief Token type, describing general class of the token. + */ + Type type; + + /** + * @brief Literal value of the token, captured directly from the query. + */ + QString value = QString::null; + + /** + * @brief Start position (first character index) of the token in the query. + */ + qint64 start; + + /** + * @brief End position (last character index) of the token in the query. + */ + qint64 end; +}; + +/** + * @brief qHash implementation for TokenPtr, so it can be used as a key in QHash and QSet. + * @param token Token that the hash code is calculated for. + * @return Unique integer value for the token. + */ +uint qHash(const TokenPtr& token); + +struct TolerantToken; +/** + * @brief Shared pointer to TolerantToken. + */ +typedef QSharedPointer TolerantTokenPtr; + +/** + * @brief Variation of token that has additional "invalid" flag. + * + * TolerantToken is used by Lexer to tolerate unfinished comments, like when you start the + * comment at the end of the query, but you never close the comment. This is tolerable case, + * while not entire correct by the syntax. + * + * In such cases the syntax highlighter must be aware of the token being invalid, so the proper + * state is marked for the paragraph. + */ +struct TolerantToken : public Token +{ + /** + * @brief Invalid state flag for the token. + */ + bool invalid = false; +}; + +/** + * @brief Ordered list of tokens. + * + * This is pretty much a QList of Token pointers, but it also provides some + * utility methods regarding this collection, which is very common in SQLiteStudio. + */ +class API_EXPORT TokenList : public QList +{ + public: + /** + * @brief Creates empty list. + */ + TokenList(); + + /** + * @brief Creates list filled with the same entries as the other list. + * @param other List to copy pointers from. + */ + TokenList(const QList& other); + + /** + * @brief Serializes contents of the list into readable form. + * @return Contents in format: {type1 value1 start1 end1} {type2 value2 start2 end2} .... + * + * Tokens are serialized with Token::toString(), then all serialized values are joined with single whitespace + * into the QString. + */ + QString toString() const; + + /** + * @brief Serializes tokens from the list into strings. + * @return List of tokens serialized into strings. + * + * Tokens are serialized with Token::toString(). + */ + QStringList toStringList() const; + + /** + * @brief Provides index of first occurrence of the token in the list. + * @param token Token to look for. + * @return Index of the token, or -1 if token was not found. + */ + int indexOf(TokenPtr token) const; + + /** + * @brief Provides index of first occurrence of the token with given type. + * @param type Toke type to look for. + * @return Index of the token, or -1 if token was not found. + */ + int indexOf(Token::Type type) const; + + /** + * @brief Provides index of first occurrence of the token with given type and value. + * @param type Token type to look for. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Index of the token, or -1 if token was not found. + */ + int indexOf(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Provides index of first occurrence of the token with given value. + * @param value Value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Index of the token, or -1 if token was not found. + */ + int indexOf(const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Provides index of last occurrence of the token in the list. + * @param token Token to look for. + * @return Index of the token, or -1 if token was not found. + */ + int lastIndexOf(TokenPtr token) const; + + /** + * @brief Provides index of last occurrence of the token with given type. + * @param type Token type to look for. + * @return Index of the token, or -1 if token was not found. + */ + int lastIndexOf(Token::Type type) const; + + /** + * @brief Provides index of last occurrence of the token with given type and value. + * @param type Token type to look for. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Index of the token, or -1 if token was not found. + */ + int lastIndexOf(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Provides index of last occurrence of the token with given value. + * @param value Value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Index of the token, or -1 if token was not found. + */ + int lastIndexOf(const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds first token with given type in the list. + * @param type Type to look for. + * @return Token found, or null pointer if it was not found. + */ + TokenPtr find(Token::Type type) const; + + /** + * @brief Finds first token with given type and value. + * @param type Type to look for. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Token found, or null pointer if it was not found. + */ + TokenPtr find(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds first token with given type and value. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Token found, or null pointer if it was not found. + */ + TokenPtr find(const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds last token with given type in the list. + * @param type Type to look for. + * @return Token found, or null pointer if it was not found. + */ + TokenPtr findLast(Token::Type type) const; + + /** + * @brief Finds last token with given type and value. + * @param type Type to look for. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Token found, or null pointer if it was not found. + */ + TokenPtr findLast(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds last token with given value. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Token found, or null pointer if it was not found. + */ + TokenPtr findLast(const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds token that according to its start and end values covers given character position. + * @param cursorPosition Position to get token for. + * @return Token found, or null pointer if it was not found. + */ + TokenPtr atCursorPosition(quint64 cursorPosition) const; + + /** + * @brief Inserts tokens at given position in this list. + * @param i Position to insert at. + * @param list List of tokens to insert. + */ + void insert(int i, const TokenList& list); + + /** + * @brief Inserts a token at given position in this list. + * @param i Position to insert at. + * @param token Token to insert. + */ + void insert(int i, TokenPtr token); + + /** + * @brief Puts all tokens from the other list to this list. + * @param other List to get tokens from. + * @return Reference to this list. + * + * It erases any previous entries in this list, just like you would normally expect for the = operator. + */ + TokenList& operator=(const QList& other); + + /** + * @brief Detokenizes all tokens from this list. + * @return String from all tokens. + * + * This is just a convenient method to call Lexer::detokenize() on this list. + */ + QString detokenize() const; + + /** + * @brief Replaces tokens on this list with other tokens. + * @param startIdx Position of the first token in this list to replace. + * @param length Number of tokens to replace. + * @param newTokens New tokens to put in place of removed ones. + */ + void replace(int startIdx, int length, const TokenList& newTokens); + + /** + * @brief Replaces tokens on this list with another token. + * @param startIdx Position of the first token in this list to replace. + * @param length Number of tokens to replace. + * @param newToken New token to put in place of removed ones. + */ + void replace(int startIdx, int length, TokenPtr newToken); + + /** + * @brief Replaces token with another token. + * @param startIdx Position of the token in this list to replace. + * @param newToken New token to put in place of removed one. + */ + void replace(int startIdx, TokenPtr newToken); + + /** + * @brief Replaces token on this list with other tokens. + * @param startIdx Position of the token in this list to replace. + * @param newTokens New tokens to put in place of removed ones. + */ + void replace(int startIdx, const TokenList& newTokens); + + /** + * @brief Replaces tokens on this list with other tokens. + * @param startToken First token to replace. + * @param endToken Last token to replace. + * @param newTokens New tokens to put in place of removed ones. + * @return Number of tokens replaced. + * + * If either \p startToken or \p endToken were not found on the list, this method does nothing + * and returns 0. + */ + int replace(TokenPtr startToken, TokenPtr endToken, const TokenList& newTokens); + + /** + * @brief Replaces tokens on this list with other tokens. + * @param startToken First token to replace. + * @param endToken Last token to replace. + * @param newToken New token to put in place of removed ones. + * @return Number of tokens replaced. + * + * If either \p startToken or \p endToken were not found on the list, this method does nothing + * and returns 0. + */ + int replace(TokenPtr startToken, TokenPtr endToken, TokenPtr newToken); + + /** + * @brief Replaces token on this list with another token. + * @param oldToken Token to replace. + * @param newToken Token to replace with. + * @return true if \p oldToken was found and replaced, or false otherwise. + */ + bool replace(TokenPtr oldToken, TokenPtr newToken); + + /** + * @brief Replaces token on this list with other tokens. + * @param oldToken Token to replace. + * @param newTokens Tokens to replace with. + * @return true if \p oldToken was found and replaced, or false otherwise. + */ + bool replace(TokenPtr oldToken, const TokenList& newTokens); + + /** + * @brief Removes tokens from the list. + * @param startToken First token to remove. + * @param endToken Last token to remove. + * @return true if \p startToken and \p endToken were found in the list and removed, or false otherwise. + * + * In case \p startToken is placed after \p endToken, this method does nothing and returns false. + */ + bool remove(TokenPtr startToken, TokenPtr endToken); + + /** + * @brief Removes first token of given type from the list. + * @param type Token type to remove. + * @return true if token was located and removed, or false otherwise. + */ + bool remove(Token::Type type); + + /** + * @brief Removes all white-space tokens from the beginning of the list. + * + * White-space tokens are tested with Token::isWhitespace(). + */ + void trimLeft(); + + /** + * @brief Removes all white-space tokens from the end of the list. + * + * White-space tokens are tested with Token::isWhitespace(). + */ + void trimRight(); + + /** + * @brief Removes all white-space tokens from both the beginning and the end of the list. + * + * White-space tokens are tested with Token::isWhitespace(). + */ + void trim(); + + /** + * @brief Removes all tokens that match given criteria from the beginning of the list. + * @param type Token type to remove. + * @param alsoTrim Token value to remove. + * + * This method is an extension to the regular trimLeft(). It removes white-space tokens, + * as well as tokens that are of given \p type and have given \p value (both conditions must be met). + */ + void trimLeft(Token::Type type, const QString& alsoTrim); + + /** + * @brief Removes all tokens that match given criteria from the end of the list. + * @param type Token type to remove. + * @param alsoTrim Token value to remove. + * + * This method is an extension to the regular trimRight(). It removes white-space tokens, + * as well as tokens that are of given \p type and have given \p value (both conditions must be met). + */ + void trimRight(Token::Type type, const QString& alsoTrim); + + /** + * @brief Removes all tokens that match given criteria from the beginning and the end of the list. + * @param type Token type to remove. + * @param alsoTrim Token value to remove. + * + * This method is an extension to the regular trim(). It removes white-space tokens, + * as well as tokens that are of given \p type and have given \p value (both conditions must be met). + */ + void trim(Token::Type type, const QString& alsoTrim); + + /** + * @brief Creates list of tokens from this list, letting through only tokens of given type. + * @param type Type of tokens to provide in the new list. + * @return List of tokens from this list matching given \p type. + */ + TokenList filter(Token::Type type) const; + + /** + * @brief Creates list of tokens from this list, letting through only tokens that are not a whitespace. + * @return List of tokens from this list that are not a whitespace. + * + * The condition to test if tokens is a whitespace is a call to Token::isWhitespace(). + */ + TokenList filterWhiteSpaces() const; + + /** + * @brief Returns sub-list of tokens from this list. + * @param pos Position to start sublist from. + * @param length Number of tokens to get from this list. If -1 (default), then all from the \p pos to the end. + * @return Sub-list of tokens from this list. + */ + TokenList mid(int pos, int length = -1) const; + + private: + /** + * @brief Finds first occurrence of token with given type. + * @param type Type of token to look for. + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findFirst() and indexOf() methods, as they share common logic. + */ + TokenPtr findFirst(Token::Type type, int* idx) const; + + /** + * @brief Finds first occurrence of token with given type and value. + * @param type Type of token to look for. + * @param value Value of the token to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findFirst() and indexOf() methods, as they share common logic. + */ + TokenPtr findFirst(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const; + + /** + * @brief Finds first occurrence of token with given value. + * @param value Value of the token to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findFirst() and indexOf() methods, as they share common logic. + */ + TokenPtr findFirst(const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const; + + /** + * @brief Finds last occurrence of token with given type. + * @param type Type of token to look for. + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findLast() and lastIndexOf() methods, as they share common logic. + */ + TokenPtr findLast(Token::Type type, int* idx) const; + + /** + * @brief Finds last occurrence of token with given type and value. + * @param type Type of token to look for. + * @param value Value of the token to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findLast() and lastIndexOf() methods, as they share common logic. + */ + TokenPtr findLast(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const; + + /** + * @brief Finds last occurrence of token with given value. + * @param value Value of the token to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findLast() and lastIndexOf() methods, as they share common logic. + */ + TokenPtr findLast(const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const; +}; + + +#endif // TOKEN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp b/SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp new file mode 100644 index 0000000..007e54c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp @@ -0,0 +1,35 @@ +#include "plugin.h" +#include "pluginloader.h" +#include "unused.h" + +AbstractPluginLoader::~AbstractPluginLoader() +{ + foreach (Plugin* plugin, plugins.values()) + { + plugin->deinit(); + } + + foreach (QPluginLoader* loader, plugins.keys()) + { + if (!loader->unload()) + qDebug() << "Could not unload plugin" << loader->fileName() << ":" << loader->errorString(); + + delete loader; + } + plugins.clear(); +} + +bool AbstractPluginLoader::add(QPluginLoader* loader, Plugin* plugin) +{ + if (!plugin->init()) + return false; + + plugins[loader] = plugin; + return true; +} + + +QList AbstractPluginLoader::getPlugins() const +{ + return plugins.values(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/pluginloader.h b/SQLiteStudio3/coreSQLiteStudio/pluginloader.h new file mode 100644 index 0000000..5f18c9a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/pluginloader.h @@ -0,0 +1,35 @@ +#ifndef PLUGINLOADER_H +#define PLUGINLOADER_H + +#include "coreSQLiteStudio_global.h" +#include +#include +#include +#include + +class Plugin; + +class API_EXPORT AbstractPluginLoader +{ + public: + virtual ~AbstractPluginLoader(); + + virtual bool add(QPluginLoader* loader, Plugin* plugin); + virtual bool test(Plugin* plugin) = 0; + QList getPlugins() const; + + private: + QHash plugins; +}; + +template +class PluginLoader : public AbstractPluginLoader +{ + public: + bool test(Plugin* plugin) + { + return (dynamic_cast(plugin) != nullptr); + } +}; + +#endif // PLUGINLOADER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp new file mode 100644 index 0000000..29397e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp @@ -0,0 +1,58 @@ +#include "builtinplugin.h" +#include "services/pluginmanager.h" +#include + +QString BuiltInPlugin::getName() const +{ + return metaObject()->className(); +} + +QString BuiltInPlugin::getTitle() const +{ + const char *title = getMetaInfo("title"); + if (!title) + return getName(); + + return title; +} + +QString BuiltInPlugin::getDescription() const +{ + return getMetaInfo("description"); +} + +int BuiltInPlugin::getVersion() const +{ + return QString(getMetaInfo("version")).toInt(); +} + +QString BuiltInPlugin::getPrintableVersion() const +{ + return PLUGINS->toPrintableVersion(getVersion()); +} + +QString BuiltInPlugin::getAuthor() const +{ + return getMetaInfo("author"); +} + +bool BuiltInPlugin::init() +{ + return true; +} + +void BuiltInPlugin::deinit() +{ +} + +const char* BuiltInPlugin::getMetaInfo(const QString& key) const +{ + for (int i = 0; i < metaObject()->classInfoCount(); i++) + { + if (key != metaObject()->classInfo(i).name()) + continue; + + return metaObject()->classInfo(i).value(); + } + return nullptr; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h new file mode 100644 index 0000000..ccb5c3d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h @@ -0,0 +1,147 @@ +#ifndef BUILTINPLUGIN_H +#define BUILTINPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugins/plugin.h" + +/** + * @brief Helper class for implementing built-in plugins + * + * This class can be inherited, so most of the abstract methods from Plugin interface get implemented. + * All details (description, name, title, author, ...) are defined using Q_CLASSINFO. + * + * There are macros defined to help you with defining those details. You don't need to define + * Q_CLASSINFO and know all required keys. You can use following macros: + *
    + *
  • ::SQLITESTUDIO_PLUGIN_TITLE
  • + *
  • ::SQLITESTUDIO_PLUGIN_DESC
  • + *
  • ::SQLITESTUDIO_PLUGIN_UI
  • + *
  • ::SQLITESTUDIO_PLUGIN_VERSION
  • + *
  • ::SQLITESTUDIO_PLUGIN_AUTHOR
  • + *
+ * + * Most of plugin implementations will use this class as a base, because it simplifies process + * of plugin development. Using this class you don't have to implement any of virtual methods + * from Plugin interface. It's enough to define meta information, like this: + * @code + * class MyPlugin : GenericPlugin + * { + * Q_OBJECT + * + * SQLITESTUDIO_PLUGIN + * SQLITESTUDIO_PLUGIN_TITLE("My plugin") + * SQLITESTUDIO_PLUGIN_DESC("Does nothing. It's an example plugin.") + * SQLITESTUDIO_PLUGIN_UI("formObjectName") + * SQLITESTUDIO_PLUGIN_VERSION(10000) + * SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + * }; + * @endcode + */class API_EXPORT BuiltInPlugin : public QObject, public virtual Plugin +{ + Q_OBJECT + Q_INTERFACES(Plugin) + + public: + /** + * @brief Provides plugin internal name. + * @return Plugin class name. + */ + QString getName() const; + + /** + * @brief Provides plugin title. + * @return Title defined in plugin's metadata file with key "title" or (if not defined) the same value as getName(). + */ + QString getTitle() const; + + /** + * @brief Provides plugin description. + * @return Description as defined in plugin's metadata file with key "description", or null QString if not defined. + */ + QString getDescription() const; + + /** + * @brief Provides plugin numeric version. + * @return Version number as defined in plugin's metadata file with key "version", or 0 if not defined. + */ + int getVersion() const; + + /** + * @brief Converts plugin version to human readable form. + * @return Version in format X.Y.Z. + */ + QString getPrintableVersion() const; + + /** + * @brief Provides an author name. + * @return Author name as defined with in plugin's metadata file with key "author", or null QString if not defined. + */ + QString getAuthor() const; + + /** + * @brief Does nothing. + * @return Always true. + * + * This is a default (empty) implementation of init() for plugins. + */ + bool init(); + + /** + * @brief Does nothing. + * + * This is a default (empty) implementation of init() for plugins. + */ + void deinit(); + + private: + /** + * @brief Extracts class meta information with given key. + * @param key Key to extract. + * @return Value of the meta information, or null if there's no information with given key. + * + * This is a helper method which queries Qt's meta object subsystem for class meta information defined with Q_CLASSINFO. + */ + const char* getMetaInfo(const QString& key) const; +}; + +/** + * @def SQLITESTUDIO_PLUGIN_TITLE + * @brief Defines plugin title. + * + * This is a built-in plugin replacement for "title" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_TITLE(Title) Q_CLASSINFO("title", Title) + +/** + * @def SQLITESTUDIO_PLUGIN_DESC + * @brief Defines plugin description. + * + * This is a built-in plugin replacement for "description" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_DESC(Desc) Q_CLASSINFO("description", Desc) + +/** + * @def SQLITESTUDIO_PLUGIN_UI + * @brief Defines Qt Designer Form object name to be used in configuration dialog. + * + * This is a built-in plugin replacement for "ui" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_UI(FormName) Q_CLASSINFO("ui", FormName) + +/** + * @def SQLITESTUDIO_PLUGIN_VERSION + * @brief Defines plugin version. + * + * This is a built-in plugin replacement for "version" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_VERSION(Ver) Q_CLASSINFO("version", #Ver) + +/** + * @def SQLITESTUDIO_PLUGIN_AUTHOR + * @brief Defines an author of the plugin. + * + * This is a built-in plugin replacement for "author" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_AUTHOR(Author) Q_CLASSINFO("author", Author) + +#endif // BUILTINPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h new file mode 100644 index 0000000..093c20e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h @@ -0,0 +1,16 @@ +#ifndef CODEFORMATTERPLUGIN_H +#define CODEFORMATTERPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugin.h" + +class Db; + +class API_EXPORT CodeFormatterPlugin : virtual public Plugin +{ + public: + virtual QString getLanguage() const = 0; + virtual QString format(const QString& code, Db* contextDb) = 0; +}; + +#endif // CODEFORMATTERPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h new file mode 100644 index 0000000..d8a8f9b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h @@ -0,0 +1,14 @@ +#ifndef CONFIGNOTIFIABLEPLUGIN_H +#define CONFIGNOTIFIABLEPLUGIN_H + +#include + +class CfgEntry; + +class ConfigNotifiablePlugin +{ + public: + virtual void configModified(CfgEntry* key, const QVariant& value) = 0; +}; + +#endif // CONFIGNOTIFIABLEPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h new file mode 100644 index 0000000..2f2e62e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h @@ -0,0 +1,73 @@ +#ifndef DBPLUGIN_H +#define DBPLUGIN_H + +#include "db/db.h" +#include "db/dbpluginoption.h" +#include "plugins/plugin.h" + +/** + * @brief Interface for database plugins + * + * This is a specialization of Plugin interface, which all database plugins have to implement. + */ +class API_EXPORT DbPlugin : virtual public Plugin +{ + public: + /** + * @brief Creates database instance defined by the plugin. + * @param name Name for the database. + * @param path Path to the database file. + * @param options Options for the database passed while registering the database in the application. + * @param errorMessage If the result is null (on failure) and this pointer is not null, the error message will be stored in it. + * @return Database instance on success, or null pointer on failure. + * + * Options can contain for example password for an encrypted database, or other connection options. + */ + virtual Db* getInstance(const QString& name, const QString& path, const QHash &options, QString* errorMessage = 0) = 0; + + /** + * @brief Provides label of what type is the database. + * @return Type label. + * + * The label is used for presenting to the user what kind of database this is. It's used on GUI + * to display database type in databases dialog. It's usually either "SQLite3" or "SQLite2", + * but it may be something else, like for example encrypted database might provide "Encrypted SQLite3", + * or something similar. + */ + virtual QString getLabel() const = 0; + + /** + * @brief Provides list of options configurable for this database plugin. + * @return List of options. + * + * DbDialog uses this to provide GUI interface, so user can configure connection options. + * For each element in the list DbDialog adds QLabel and the input widget for entering option value. + * Option values entered by user are later passed to getInstance() as second argument. + */ + virtual QList getOptionsList() const = 0; + + /** + * @brief Generates suggestion for database name. + * @param baseValue Value enterd as file path in DbDialog. + * @return Generated name suggestion. + * + * This can be simply string representation of \p baseValue, + * but the plugin may add something characteristic for the plugin. + */ + virtual QString generateDbName(const QVariant& baseValue) = 0; + + /** + * @brief Tests if the given database support is provided by this plugin. + * @param db Database to test. + * @return true if the database is supported by this plugin, or false otherwise. + * + * Implementation of this method should check if given database object + * is of the same type, that those returned from getInstance(). + * + * This method is used by DbManager to find out which databases are supported by which plugins, + * so when some plugin is about to be unloaded, all its databases are closed properly first. + */ + virtual bool checkIfDbServedByPlugin(Db* db) const = 0; +}; + +#endif // DBPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp new file mode 100644 index 0000000..0f16098 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp @@ -0,0 +1,47 @@ +#include "dbpluginsqlite3.h" +#include "db/dbsqlite3.h" +#include "common/unused.h" +#include + +Db* DbPluginSqlite3::getInstance(const QString& name, const QString& path, const QHash& options, QString* errorMessage) +{ + UNUSED(errorMessage); + Db* db = new DbSqlite3(name, path, options); + + if (!db->openForProbing()) + { + delete db; + return nullptr; + } + + SqlQueryPtr results = db->exec("SELECT * FROM sqlite_master"); + if (results->isError()) + { + delete db; + return nullptr; + } + + db->closeQuiet(); + return db; +} + +QString DbPluginSqlite3::getLabel() const +{ + return "SQLite 3"; +} + +QList DbPluginSqlite3::getOptionsList() const +{ + return QList(); +} + +QString DbPluginSqlite3::generateDbName(const QVariant& baseValue) +{ + QFileInfo file(baseValue.toString()); + return file.baseName(); +} + +bool DbPluginSqlite3::checkIfDbServedByPlugin(Db* db) const +{ + return (db && dynamic_cast(db)); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h new file mode 100644 index 0000000..ee522d7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h @@ -0,0 +1,24 @@ +#ifndef DBPLUGINSQLITE3_H +#define DBPLUGINSQLITE3_H + +#include "dbplugin.h" +#include "builtinplugin.h" + +class DbPluginSqlite3 : public BuiltInPlugin, public DbPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("SQLite 3") + SQLITESTUDIO_PLUGIN_DESC("SQLite 3 databases support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + Db* getInstance(const QString& name, const QString& path, const QHash& options, QString* errorMessage); + QString getLabel() const; + QList getOptionsList() const; + QString generateDbName(const QVariant& baseValue); + bool checkIfDbServedByPlugin(Db* db) const; +}; + +#endif // DBPLUGINSQLITE3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h new file mode 100644 index 0000000..06aded7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h @@ -0,0 +1,372 @@ +#ifndef EXPORTPLUGIN_H +#define EXPORTPLUGIN_H + +#include "plugin.h" +#include "db/sqlquery.h" +#include "db/queryexecutor.h" +#include "services/exportmanager.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" + +class CfgMain; + +/** + * @brief Provides support for particular export format. + * + * All export methods in this class should report any warnings, error messages, etc through the NotifyManager, + * that is by using notifyError() and its family methods. + */ +class ExportPlugin : virtual public Plugin +{ + public: + /** + * @brief Provides name of the format that the plugin exports to. + * @return Format name that will be displayed on UI. + * + * Format must be a single word name. + */ + virtual QString getFormatName() const = 0; + + /** + * @brief Tells what standard exporting options should be displayed to the user on UI. + * @return OR-ed set of option enum values. + */ + virtual ExportManager::StandardConfigFlags standardOptionsToEnable() const = 0; + + /** + * @brief Tells which character encoding use by default in export dialog. + * @return Name of the encoding. + * + * If the plugin doesn't return ExportManager::CODEC in results from standardOptionsToEnable(), then result + * of this function is ignored and it can return null string. + */ + virtual QString getDefaultEncoding() const = 0; + + /** + * @brief Provides set of modes supported by this export plugin. + * @return OR-ed set of supported modes. + * + * Some export plugins might not support some of exporting modes. For example CSV export plugin + * will not support DATABASE exporting, because CSV cannot represent schema of the database. + * + * If a plugin doesn't return some mode in this method, then that plugin will be excluded + * from list of available formats to export, when user requests to export in this mode. + */ + virtual ExportManager::ExportModes getSupportedModes() const = 0; + + /** + * @brief Provides set of flags for additional information that needs to be provided for this plugin. + * @return OR-ed set of flags. + * + * Some plugins might need to know what is a total number of rows that are expected to be + * exported for each table or query results. Other plugins might want to know + * what is the maximum number of characters/bytes in each exported table column, + * so they can calculate column widths when drawing them in the exported files, documents, etc. + * + * Those additional information are not provided by default, because they are gathered with extra queries + * to the database and for huge tables it might cause the table to be exported much longer, even if + * those information wasn't required by some plugin. + * + * See ExportManager::ExportProviderFlags for list of possible flags and what they mean. + */ + virtual ExportManager::ExportProviderFlags getProviderFlags() const = 0; + + /** + * @brief Provides config object that holds configuration for exporting. + * @return Config object, or null if the exporting with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of exporting in given mode. + * @param mode Mode for which the form is requested for. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If exporting with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getExportConfigFormName() const = 0; + + /** + * @brief Tells plugin what is going to be the next export mode. + * @param mode Mode that the next export is going to be performed for. + * + * Plugin should remember this and use later when some logic is depended on what the mode is. + * For example getConfigFormName() (as well as getConfig()) might return different config forms + * for different modes. + */ + virtual void setExportMode(ExportManager::ExportMode mode) = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling EXPORT_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + */ + virtual void validateOptions() = 0; + + /** + * @brief Provides usual file name extension used with this format. + * @return File name extension (like ".csv"). + * + * This extension will be automatically appended to file name when user picked file name + * with file dialog, but the file extension part in the selected file was ommited. + */ + virtual QString defaultFileExtension() const = 0; + + /** + * @brief Mime type for when exporting binary format to clipboard. + * @return Mime type, like "image/png". + * + * Value returned from this method is used to set mime type when the exporting is done into the system clipboard. + * The clipboard needs mime type to identify what kind of data is in it. + * + * See details http://qt-project.org/doc/qt-5/qmimedata.html#setData + * + * If the plugin exports just a string, then this method can return QString::null and SqliteStudio will assume + * that the data is of "text/plain" type. + */ + virtual QString getMimeType() const = 0; + + /** + * @brief Tells if the data being exported is a binary or text. + * @return true for binary data, false for textual data. + * + * This is used by the SQLiteStudio to configure output device properly. For example CSV format is textual, + * but PNG is considered binary. + */ + virtual bool isBinaryData() const = 0; + + /** + * @brief Provides common state values before the export process begins. + * @param db Database that the export will be performed on. + * @param output Output device to write exporting data to. + * @param config Common exporting configuration, like file name, codec, etc. + * @return true for success, or false in case of a fatal error. + * + * This is called exactly once before every export process (that is once per each export called by user). + * Use it to remember database, output device, config for further method calls, or write a file header. + * This method will be followed by any of *export*() methods from this interface. + * + * There's a convenient class GenericExportPlugin, which you can extend instead of ExportPlugin. If you use + * GenericExportPlugin for a base class of exprt plugin, then this method is already implemented there + * and it stores all these parameters in protected class members so you can use them in other methods. + */ + virtual bool initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config) = 0; + + /** + * @brief Does initial entry for exported query results. + * @param query Query that was executed to get the results. + * @param columns Columns returned from the query. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + * + * It's called just before actual data entries are exported (with exportQueryResultsRow()). + * It's called exactly once for single query results export. + */ + virtual bool beforeExportQueryResults(const QString& query, QList& columns, + const QHash providedData) = 0; + + /** + * @brief Does export entry for a single row of data. + * @param row Single data row. + * @return true for success, or false in case of a fatal error. + * + * It's called for each data row returned from the query. + */ + virtual bool exportQueryResultsRow(SqlResultsRowPtr row) = 0; + + /** + * @brief Does final entry for exported query results. + * @return true for success, or false in case of a fatal error. + * + * It's called once after all data from the query was exported. + */ + virtual bool afterExportQueryResults() = 0; + + /** + * @brief Prepares for exporting tables from database. + * @return true for success, or false in case of a fatal error. + * + * This is called only for database export. For single table export only exportTable() is called. + */ + virtual bool beforeExportTables() = 0; + + /** + * @brief Does initial entry for exported table. + * @param database "Attach" name of the database that the table belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the table to export. + * @param columnNames Name of columns in the table, in order they will appear in the rows passed to exportTableRow(). + * @param ddl The DDL of the table. + * @param createTable Table DDL parsed into an object. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + */ + virtual bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash providedData) = 0; + + /** + * @brief Does initial entry for exported virtual table. + * @param database "Attach" name of the database that the table belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the table to export. + * @param columnNames Name of columns in the table, in order they will appear in the rows passed to exportTableRow(). + * @param ddl The DDL of the table. + * @param createTable Table DDL parsed into an object. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + */ + virtual bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, + SqliteCreateVirtualTablePtr createTable, const QHash providedData) = 0; + + /** + * @brief Does export entry for a single row of data. + * @param data Single data row. + * @return true for success, or false in case of a fatal error. + * + * This method will be called only if StandardExportConfig::exportData in initBeforeExport() was true. + */ + virtual bool exportTableRow(SqlResultsRowPtr data) = 0; + + /** + * @brief Does final entry for exported table, after its data was exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportTable() = 0; + + /** + * @brief Does final entries after all tables have been exported. + * @return true for success, or false in case of a fatal error. + * + * This is called only for database export. For single table export only exportTable() is called. + * After table exporting also an afterExport() is called, so you can use that for any postprocessing. + */ + virtual bool afterExportTables() = 0; + + /** + * @brief Does initial entry for the entire database export. + * @param database Database name (as listed in database list). + * @return true for success, or false in case of a fatal error. + * + * It's called just once, before each database object gets exported. + * This method will be followed by calls to (in this order): beforeExportTables(), exportTable(), exportVirtualTable(), exportTableRow(), afterExportTable(), + * afterExportTables(), beforeExportIndexes(), exportIndex(), afterExportIndexes(), beforeExportTriggers(), exportTrigger(), afterExportTriggers(), + * beforeExportViews(), exportView(), afterExportViews() and afterExportDatabase(). + * Note, that exportTableRow() will be called only if StandardExportConfig::exportData in initBeforeExport() was true. + */ + virtual bool beforeExportDatabase(const QString& database) = 0; + + /** + * @brief Prepares for exporting indexes from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportIndexes() = 0; + + /** + * @brief Does entire export entry for an index. + * @param database "Attach" name of the database that the index belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the index to export. + * @param ddl The DDL of the index. + * @param createIndex Index DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for index export. + */ + virtual bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) = 0; + + /** + * @brief Does final entries after all indexes have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportIndexes() = 0; + + /** + * @brief Prepares for exporting triggers from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportTriggers() = 0; + + /** + * @brief Does entire export entry for an trigger. + * @param database "Attach" name of the database that the trigger belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the trigger to export. + * @param ddl The DDL of the trigger. + * @param createTrigger Trigger DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for trigger export. + */ + virtual bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) = 0; + + /** + * @brief Does final entries after all triggers have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportTriggers() = 0; + + /** + * @brief Prepares for exporting views from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportViews() = 0; + + /** + * @brief Does entire export entry for an view. + * @param database "Attach" name of the database that the view belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the trigger to view. + * @param ddl The DDL of the view. + * @param createView View DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for view export. + */ + virtual bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view) = 0; + + /** + * @brief Does final entries after all views have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportViews() = 0; + + /** + * @brief Does final entry for the entire database export. + * @return true for success, or false in case of a fatal error. + * + * It's called just once, after all database object get exported. + */ + virtual bool afterExportDatabase() = 0; + + /** + * @brief Does final entry for any export process. + * @return true for success, or false in case of a fatal error. + * + * This is similar to afterExportDatabase() when the export mode is database, + * but this is called at the end for any export mode, not only for database export. + * + * Use it to write a footer, or anything like that. + */ + virtual bool afterExport() = 0; + + /** + * @brief Called after every export, even failed one. + * + * Implementation of this method should cleanup any resources used during each single export process. + * This method is guaranteed to be executed, no matter if export was successful or not. + */ + virtual void cleanupAfterExport() = 0; +}; + +#endif // EXPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h new file mode 100644 index 0000000..b50834a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h @@ -0,0 +1,20 @@ +#ifndef GENERALPURPOSEPLUGIN_H +#define GENERALPURPOSEPLUGIN_H + +#include "plugin.h" + +/** + * @brief The general purpose plugin interface. + * + * General purpose plugins are not designated for some specific function. + * They rely on init() and deinit() implementations to add some menubar entries, + * or toolbar entries (or anything else), so user can interact with the plugin. + * + * @see Plugin + * @see GenericPlugin + */ +class API_EXPORT GeneralPurposePlugin : virtual public Plugin +{ +}; + +#endif // GENERALPURPOSEPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp new file mode 100644 index 0000000..d191ae4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp @@ -0,0 +1,160 @@ +#include "genericexportplugin.h" +#include "common/utils.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include "config_builder.h" +#include + +bool GenericExportPlugin::initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config) +{ + this->db = db; + this->output = output; + this->config = &config; + + if (standardOptionsToEnable().testFlag(ExportManager::CODEC)) + { + codec = codecForName(this->config->codec); + if (!codec) + { + codec = defaultCodec(); + notifyWarn(tr("Could not initialize text codec for exporting. Using default codec: %1").arg(QString::fromLatin1(codec->name()))); + } + } + + return beforeExport(); +} + +ExportManager::ExportModes GenericExportPlugin::getSupportedModes() const +{ + return ExportManager::FILE|ExportManager::CLIPBOARD|ExportManager::DATABASE|ExportManager::TABLE|ExportManager::QUERY_RESULTS; +} + +ExportManager::ExportProviderFlags GenericExportPlugin::getProviderFlags() const +{ + return ExportManager::NONE; +} + +QString GenericExportPlugin::getExportConfigFormName() const +{ + return QString(); +} + +CfgMain* GenericExportPlugin::getConfig() +{ + return nullptr; +} + +QString GenericExportPlugin::getConfigFormName(ExportManager::ExportMode mode) const +{ + UNUSED(mode); + return QString::null; +} + +QString GenericExportPlugin::getMimeType() const +{ + return QString::null; +} + +QString GenericExportPlugin::getDefaultEncoding() const +{ + return QString(); +} + +bool GenericExportPlugin::isBinaryData() const +{ + return false; +} + +void GenericExportPlugin::setExportMode(ExportManager::ExportMode mode) +{ + this->exportMode = mode; +} + +bool GenericExportPlugin::afterExportQueryResults() +{ + return true; +} + +bool GenericExportPlugin::afterExportTable() +{ + return true; +} + +bool GenericExportPlugin::initBeforeExport() +{ + return true; +} + +void GenericExportPlugin::write(const QString& str) +{ + output->write(codec->fromUnicode(str)); +} + +void GenericExportPlugin::writeln(const QString& str) +{ + write(str + "\n"); +} + +bool GenericExportPlugin::isTableExport() const +{ + return exportMode == ExportManager::TABLE; +} + +bool GenericExportPlugin::beforeExportTables() +{ + return true; +} + +bool GenericExportPlugin::afterExportTables() +{ + return true; +} + +bool GenericExportPlugin::beforeExportIndexes() +{ + return true; +} + +bool GenericExportPlugin::afterExportIndexes() +{ + return true; +} + +bool GenericExportPlugin::beforeExportTriggers() +{ + return true; +} + +bool GenericExportPlugin::afterExportTriggers() +{ + return true; +} + +bool GenericExportPlugin::beforeExportViews() +{ + return true; +} + +bool GenericExportPlugin::afterExportViews() +{ + return true; +} + +bool GenericExportPlugin::afterExportDatabase() +{ + return true; +} + +bool GenericExportPlugin::afterExport() +{ + return true; +} + +void GenericExportPlugin::cleanupAfterExport() +{ +} + +bool GenericExportPlugin::beforeExport() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h new file mode 100644 index 0000000..1719baa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h @@ -0,0 +1,56 @@ +#ifndef GENERICEXPORTPLUGIN_H +#define GENERICEXPORTPLUGIN_H + +#include "exportplugin.h" +#include "genericplugin.h" + +class API_EXPORT GenericExportPlugin : virtual public GenericPlugin, public ExportPlugin +{ + public: + bool initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config); + ExportManager::ExportModes getSupportedModes() const; + ExportManager::ExportProviderFlags getProviderFlags() const; + QString getExportConfigFormName() const; + CfgMain* getConfig(); + QString getConfigFormName(ExportManager::ExportMode exportMode) const; + QString getMimeType() const; + QString getDefaultEncoding() const; + bool isBinaryData() const; + void setExportMode(ExportManager::ExportMode exportMode); + bool afterExportQueryResults(); + bool afterExportTable(); + bool beforeExportTables(); + bool afterExportTables(); + bool beforeExportIndexes(); + bool afterExportIndexes(); + bool beforeExportTriggers(); + bool afterExportTriggers(); + bool beforeExportViews(); + bool afterExportViews(); + bool afterExportDatabase(); + bool afterExport(); + void cleanupAfterExport(); + + /** + * @brief Does the initial entry in the export. + * @return true for success, or false in case of a fatal error. + * + * Use it to write the header, or something like that. Default implementation does nothing. + * This is called from initBeforeExport(), after exportMode and other settings are already prepared. + */ + virtual bool beforeExport(); + + protected: + virtual bool initBeforeExport(); + void write(const QString& str); + void writeln(const QString& str); + bool isTableExport() const; + + Db* db = nullptr; + QIODevice* output = nullptr; + const ExportManager::StandardExportConfig* config = nullptr; + QTextCodec* codec = nullptr; + ExportManager::ExportMode exportMode = ExportManager::UNDEFINED; +}; + +#endif // GENERICEXPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp new file mode 100644 index 0000000..899691c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp @@ -0,0 +1,67 @@ +#include "genericplugin.h" +#include "services/pluginmanager.h" +#include + +QString GenericPlugin::getName() const +{ + return metaData["name"].toString(); +} + +QString GenericPlugin::getTitle() const +{ + if (!metaData["title"].isValid()) + return getName(); + + return metaData["title"].toString(); +} + +CfgMain* GenericPlugin::getMainUiConfig() +{ + return nullptr; +} + +QString GenericPlugin::getDescription() const +{ + return metaData["description"].toString(); +} + +int GenericPlugin::getVersion() const +{ + return metaData["version"].toInt(); +} + +QString GenericPlugin::getPrintableVersion() const +{ + return PLUGINS->toPrintableVersion(getVersion()); +} + +bool GenericPlugin::init() +{ + return true; +} + +void GenericPlugin::deinit() +{ +} + +void GenericPlugin::loadMetaData(const QJsonObject& metaData) +{ + this->metaData = PLUGINS->readMetaData(metaData); +} + +const char* GenericPlugin::getMetaInfo(const QString& key) const +{ + for (int i = 0; i < metaObject()->classInfoCount(); i++) + { + if (key != metaObject()->classInfo(i).name()) + continue; + + return metaObject()->classInfo(i).value(); + } + return nullptr; +} + +QString GenericPlugin::getAuthor() const +{ + return metaData["author"].toString(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h new file mode 100644 index 0000000..ce008e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h @@ -0,0 +1,126 @@ +#ifndef GENERICPLUGIN_H +#define GENERICPLUGIN_H + +#include "plugin.h" +#include +#include +#include +#include + +/** @file */ + +/** + * @brief Helper class for implementing plugins + * + * This class can be inherited, so most of the abstract methods from Plugin interface get implemented. + * All details (description, name, title, author, ...) are defined in separate json file. + * + * Most of plugin implementations will use this class as a base, because it simplifies process + * of plugin development. Using this class you don't have to implement any of virtual methods + * from Plugin interface. It's enough to define meta information in the json file, like this: + * @code + * { + * "type": "ScriptingPlugin", + * "title": "My plugin", + * "description": "Does nothing. It's an example plugin.", + * "version": 10000 + * "author": "sqlitestudio.pl" + * }; + * @endcode + * + * and then just declare the class as SQLiteStudio plugin, pointing the json file you just created: + * @code + * class MyPlugin : public GenericPlugin, public ScriptingPlugin + * { + * Q_OBJECT + * SQLITESTUDIO_PLUGIN("myplugin.json") + * + * // rest of the class + * }; + * @endcode + */ +class API_EXPORT GenericPlugin : public QObject, public virtual Plugin +{ + Q_OBJECT + Q_INTERFACES(Plugin) + + public: + /** + * @brief Provides plugin internal name. + * @return Plugin class name. + */ + QString getName() const; + + /** + * @brief Provides plugin title. + * @return Title defined in plugin's metadata file with key "title" or (if not defined) the same value as getName(). + */ + QString getTitle() const; + + /** + * @brief Provides configuration object to use in ConfigDialog. + * @return This implementation always returns null. + */ + CfgMain* getMainUiConfig(); + + /** + * @brief Provides plugin description. + * @return Description as defined in plugin's metadata file with key "description", or null QString if not defined. + */ + QString getDescription() const; + + /** + * @brief Provides plugin numeric version. + * @return Version number as defined in plugin's metadata file with key "version", or 0 if not defined. + */ + int getVersion() const; + + /** + * @brief Converts plugin version to human readable format. + * @return Version in format X.Y.Z. + */ + QString getPrintableVersion() const; + + /** + * @brief Provides an author name. + * @return Author name as defined with in plugin's metadata file with key "author", or null QString if not defined. + */ + QString getAuthor() const; + + /** + * @brief Does nothing. + * @return Always true. + * + * This is a default (empty) implementation of init() for plugins. + */ + bool init(); + + /** + * @brief Does nothing. + * + * This is a default (empty) implementation of init() for plugins. + */ + void deinit(); + + /** + * @brief Loads metadata from given Json object. + * @param The metadata from json file. + * + * This is called by PluginManager. + */ + void loadMetaData(const QJsonObject& metaData); + + private: + /** + * @brief Extracts class meta information with given key. + * @param key Key to extract. + * @return Value of the meta information, or null if there's no information with given key. + * + * This is a helper method which queries Qt's meta object subsystem for class meta information defined with Q_CLASSINFO. + */ + const char* getMetaInfo(const QString& key) const; + + QHash metaData; +}; + +#endif // GENERICPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h new file mode 100644 index 0000000..ff520cd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h @@ -0,0 +1,132 @@ +#ifndef IMPORTPLUGIN_H +#define IMPORTPLUGIN_H + +#include "plugin.h" +#include "services/importmanager.h" + +class QIODevice; +class CfgMain; + +/** + * @brief Provides support for particular import format. + * + * All import methods in this class should report any warnings, error messages, etc through the NotifyManager, + * that is by using notifyError() and its family methods. + */ +class ImportPlugin : virtual public Plugin +{ + public: + /** + * @brief Pair of column name and its data type. + */ + typedef QPair ColumnDefinition; + + /** + * @brief Used to show this plugin in the combo of data source types in the import dialog. + * @return String representing this plugin in the import dialog. + */ + virtual QString getDataSourceTypeName() const = 0; + + /** + * @brief Tells which standard import options should be available on to the user. + * @return OR-ed set of standard option enums. + */ + virtual ImportManager::StandardConfigFlags standardOptionsToEnable() const = 0; + + /** + * @brief Provides file name filter for file dialog. + * @return Filter compliant with QFileDialog documentation. + * + * If your plugin does not return ImportManager::FILE_NAME, this method can simply return QString::null. + * If your plugin does use input file name, then this method can (but don't have to) return file name filter + * to match expected files when user browses for the input file. + * + * The filter value (if not null) is passed directly to the QFileDialog. + */ + virtual QString getFileFilter() const = 0; + + /** + * @brief Called before each import that user makes. + * @param config Standard options configured by user in the import dialog. + * @return true if everything looks fine from plugin's perspective, or false otherwise. + * + * In case there were some problems at this step, plugin should return false, but before that it should tell what was wrong using NotifyManager's global shortcut + * method: notifyError(). + */ + virtual bool beforeImport(const ImportManager::StandardImportConfig& config) = 0; + + /** + * @brief Called after import process has been finished (successfully or not). + * + * Implement this method to clean up any resources that the plugin has initialized before. + */ + virtual void afterImport() = 0; + + /** + * @brief Provides list of columns (with their datatypes) for the data to be imported. + * @return List of columns, each column consists of column name and its data type definition. + * + * The ColumnDefinition is actually a QPair of two QString types. First in the pair is column name, second is column's data type, + * as a string representation. + * + * Let's say your plugin wants to import data that fits into 2 columns, first of INTEGER type and the second of VARCHAR(0, 5) type. + * You would write it like this: + * @code + * QList list; + * list << ColumnDefinition("column1", "INTEGER"); + * list << ColumnDefinition("column2", "VARCHAR (0, 5)"); + * return list; + * @endcode + */ + virtual QList getColumns() const = 0; + + /** + * @brief Provides next set of data from the data source. + * @return List of values, where number of elements must be equal to number of columns returned from getColumns(). + * + * This is essential import plugin method. It provides the data. + * This method simply provides next row of the data for a table. + * It will be called again and again, until it returns empty list, which will be interpreted as the end of data to import. + */ + virtual QList next() = 0; + + /** + * @brief Provides config object that holds configuration for importing. + * @return Config object, or null if the importing with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of import dialog. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If importing with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getImportConfigFormName() const = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * @return true when validation was successful, or false if any error occured. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling IMPORT_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + * + * In this method you can also call IMPORT_MANAGER->configStateUpdateFromPlugin() to adjust options UI + * to the current config values. + */ + virtual bool validateOptions() = 0; +}; + +#endif // IMPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h new file mode 100644 index 0000000..183adf7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h @@ -0,0 +1,182 @@ +#ifndef PLUGIN_H +#define PLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include +#include +#include + +class PluginType; +class CfgMain; + +/** @file */ + +/** + * @brief General plugin interface. + * + * This is the top-most generic interface for SQLiteStudio plugins. + * It's based in Qt's plugins framework. Every SQLiteStudio plugin must + * implement this (or its descendant) interface. + * + * SQLiteStudio plugin is basicly class implementing this interface, + * compiled as shared library (*.dll, *.so, *.dylib). + * + * Apart from implementing Plugin interface, the plugin class must also declare ::SQLITESTUDIO_PLUGIN macro, like this: + * @code + * class MyPlugin : Plugin + * { + * Q_OBJECT + * + * SQLITESTUDIO_PLUGIN + * + * public: + * // ... + * }; + * @endcode + * + * Full tutorial for writting plugins is at: http://wiki.sqlitestudio.pl/index.php/Writting_plugins + * + * SQLiteStudio looks for plugins in following directories: + *
    + *
  • {current_executable_dir}/plugins - a "plugins" subdirectory of the directory where application binary is placed,
  • + *
  • {configuration_dir}/plugins - a "plugins" subdirectory of configuration directory detected and defined in Config,
  • + *
  • {env_var:SQLITESTUDIO_PLUGINS} - environment variable with name "SQLITESTUDIO_PLUGINS",
  • + *
  • {compile_time:PLUGINS_DIR} - compile time defined parameter's value of parameter with the name "PLUGINS_DIR".
  • + *
+ */ +class API_EXPORT Plugin +{ + public: + /** + * @brief Virtual destructor to make sure all plugins are destroyed correctly. + */ + virtual ~Plugin() {} + + /** + * @brief Gets name of the plugin. + * @return Name of the plugin. + * + * The name of the plugin is a kind of primary key for plugins. It has to be unique across all loaded plugins. + * An attempt to load two plugins with the same name will result in failed load of the second plugin. + * + * The name is a kind of internal plugin's name. It's designated for presenting to the user + * - for that purpose there is a getTitle(). + * + * It's a good practice to keep it as single word. Providing plugin's class name can be a good idea. + * + * BUG: Currently this implementation of this method has to always return the name of the plugin's main implementation class + * (like DbSqlite2), otherwise SQLiteStudio will either unable to load it, or dependencies to this plugin will fail. + * This has to do with PluginManagerImpl relying on "className" entry returned from QPluginLoader's metadata. + */ + virtual QString getName() const = 0; + + /** + * @brief Gets title for the plugin. + * @return Plugin title. + * + * This is plugin's name to be presented to the user. It can be multiple words name. It should be localized (translatable) text. + * It's used solely for presenting plugin to the user, nothing more. + */ + virtual QString getTitle() const = 0; + + /** + * @brief Provides name of the plugin's author. + * @return Author name. + * + * This is displayed in ConfigDialog when user clicks on Details button of the plugin. + */ + virtual QString getAuthor() const = 0; + + /** + * @brief Provides some details on what does the plugin. + * @return Plugin description. + * + * This is displayed in ConfigDialog when user clicks on Details button of the plugin. + */ + virtual QString getDescription() const = 0; + + /** + * @brief Provides plugin version number. + * @return Version number. + * + * Version number format can be picked by plugin developer, but it is recommended + * to use XXYYZZ, where XX is major version, YY is minor version and ZZ is patch version. + * Of course the XX can be single X if major version is less then 10. + * + * This would result in versions like: 10000 (for version 1.0.0), or 10102 (for version 1.1.2), + * or 123200 (for version 12.32.0). + * + * This is of course just a suggestion, you don't have to stick to it. Just keep in mind, + * that this number is used by SQLiteStudio to compare plugin versions. If there's a plugin with higher version, + * SQLiteStudio will propose to update it. + * + * The suggested format is also easier to convert to printable (string) version later in getPrintableVersion(). + */ + virtual int getVersion() const = 0; + + /** + * @brief Provides formatted version string. + * @return Version string. + * + * It provides string that represents version returned from getVersion() in a human-readable form. + * It's a good practice to return versions like "1.3.2", or "1.5", as they are easy to read. + * + * This version string is presented to the user. + */ + virtual QString getPrintableVersion() const = 0; + + /** + * @brief Initializes plugin just after it was loaded. + * @return true on success, or false otherwise. + * + * This is called as a first, just after plugin was loaded. If it returns false, + * then plugin loading is considered to be failed and gets unloaded. + * + * If this method returns false, then deinit() is not called. + */ + virtual bool init() = 0; + + /** + * @brief Deinitializes plugin that is about to be unloaded. + * + * This is called just before plugin is unloaded. It's called only when plugin was loaded + * successfully. It's NOT called when init() returned false. + */ + virtual void deinit() = 0; +}; + +/** + * @def SqliteStudioPluginInterface + * @brief SQLiteStudio plugin interface ID. + * + * This is an ID string for Qt's plugins framework. It's used by ::SQLITESTUDIO_PLUGIN macro. + * No need to use it directly. + */ +#define SqliteStudioPluginInterface "pl.sqlitestudio.Plugin/1.0" + +/** + * @def SQLITESTUDIO_PLUGIN + * @brief Defines class as a SQLiteStudio plugin + * + * Every class implementing SQLiteStudio plugin must have this declaration, + * otherwise SQLiteStudio won't be able to load the plugin. + * + * It has to be placed in class declaration: + * @code + * class MyPlugin : public QObject, public Plugin + * { + * Q_OBJECT + * SQLITESTUDIO_PLUGIN + * + * public: + * // ... + * } + * @endcode + */ +#define SQLITESTUDIO_PLUGIN(file)\ + Q_PLUGIN_METADATA(IID SqliteStudioPluginInterface FILE file) \ + Q_INTERFACES(Plugin) + +Q_DECLARE_INTERFACE(Plugin, SqliteStudioPluginInterface) + +#endif // PLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp new file mode 100644 index 0000000..39988bd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp @@ -0,0 +1,45 @@ +#include "pluginsymbolresolver.h" +#include +#include + +PluginSymbolResolver::PluginSymbolResolver() +{ +} + +void PluginSymbolResolver::addFileNameMask(const QString &mask) +{ + nameFilters << mask; +} + +void PluginSymbolResolver::addLookupSubFolder(const QString &name) +{ + subFolders << name; +} + +bool PluginSymbolResolver::load() +{ + QStringList paths = qApp->libraryPaths(); + foreach (QString path, paths) + foreach (QString subFolder, subFolders) + paths << path + "/" + subFolder; + + foreach (QString path, paths) + { + QDir dir(path); + foreach (QString file, dir.entryList(nameFilters)) + { + lib.setFileName(path+"/"+file); + if (lib.load()) + break; + } + if (lib.isLoaded()) + break; + } + + return lib.isLoaded(); +} + +QFunctionPointer PluginSymbolResolver::resolve(const char *symbol) +{ + return lib.resolve(symbol); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h new file mode 100644 index 0000000..da6c62e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h @@ -0,0 +1,24 @@ +#ifndef PLUGINSYMBOLRESOLVER_H +#define PLUGINSYMBOLRESOLVER_H + +#include "coreSQLiteStudio_global.h" +#include +#include + +class API_EXPORT PluginSymbolResolver +{ + public: + PluginSymbolResolver(); + + void addFileNameMask(const QString& mask); + void addLookupSubFolder(const QString& name); + bool load(); + QFunctionPointer resolve(const char* symbol); + + private: + QStringList nameFilters; + QStringList subFolders; + QLibrary lib; +}; + +#endif // PLUGINSYMBOLRESOLVER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp new file mode 100644 index 0000000..57c69c0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp @@ -0,0 +1,52 @@ +#include "plugin.h" +#include "plugintype.h" +#include "services/pluginmanager.h" +#include + +PluginType::PluginType(const QString& title, const QString& form) : + title(title), configUiForm(form) +{ +} + + +PluginType::~PluginType() +{ +} + +QString PluginType::getName() const +{ + return name; +} + +void PluginType::setNativeName(const QString& nativeName) +{ + name = nativeName; + while (name.at(0).isDigit()) + name = name.mid(1); +} +QString PluginType::getTitle() const +{ + return title; +} + +QString PluginType::getConfigUiForm() const +{ + return configUiForm; +} + +QList PluginType::getLoadedPlugins() const +{ + PluginType* type = const_cast(this); + return PLUGINS->getLoadedPlugins(type); +} + +QStringList PluginType::getAllPluginNames() const +{ + PluginType* type = const_cast(this); + return PLUGINS->getAllPluginNames(type); +} + +bool PluginType::nameLessThan(PluginType* type1, PluginType* type2) +{ + return type1->title.compare(type2->title) < 0; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h new file mode 100644 index 0000000..1002ac8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h @@ -0,0 +1,63 @@ +#ifndef PLUGINTYPE_H +#define PLUGINTYPE_H + +#include "coreSQLiteStudio_global.h" +#include +#include +#include + +class Plugin; + +template +class DefinedPluginType; + +class API_EXPORT PluginType +{ + public: + virtual ~PluginType(); + + QString getName() const; + QString getTitle() const; + QString getConfigUiForm() const; + QList getLoadedPlugins() const; + QStringList getAllPluginNames() const; + + virtual bool test(Plugin* plugin) = 0; + + template + bool isForPluginType() + { + return dynamic_cast*>(this) != nullptr; + } + + static bool nameLessThan(PluginType* type1, PluginType* type2); + + protected: + PluginType(const QString& title, const QString& form); + void setNativeName(const QString& nativeName); + + QString title; + QString configUiForm; + QString name; +}; + + +template +class DefinedPluginType : public PluginType +{ + friend class PluginManager; + + public: + bool test(Plugin* plugin) + { + return (dynamic_cast(plugin) != nullptr); + } + + protected: + DefinedPluginType(const QString& title, const QString& form) : PluginType(title, form) + { + setNativeName(typeid(T).name()); + } +}; + +#endif // PLUGINTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp new file mode 100644 index 0000000..75c213f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp @@ -0,0 +1,48 @@ +#include "populateconstant.h" +#include "common/unused.h" + +PopulateConstant::PopulateConstant() +{ +} + +QString PopulateConstant::getTitle() const +{ + return tr("Constant", "populate constant plugin name"); +} + +PopulateEngine*PopulateConstant::createEngine() +{ + return new PopulateConstantEngine(); +} + +bool PopulateConstantEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + return true; +} + +QVariant PopulateConstantEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + return cfg.PopulateConstant.Value.get(); +} + +void PopulateConstantEngine::afterPopulating() +{ +} + +CfgMain*PopulateConstantEngine::getConfig() +{ + return &cfg; +} + +QString PopulateConstantEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateConstantConfig"); +} + +bool PopulateConstantEngine::validateOptions() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h new file mode 100644 index 0000000..1061519 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h @@ -0,0 +1,44 @@ +#ifndef POPULATECONSTANT_H +#define POPULATECONSTANT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateConstantConfig, + CFG_CATEGORY(PopulateConstant, + CFG_ENTRY(QString, Value, QString()) + ) +) + +class PopulateConstant : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Constant") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with a constant value.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateConstant(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateConstantEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateConstantConfig, cfg) +}; + +#endif // POPULATECONSTANT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui new file mode 100644 index 0000000..39e80e1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui @@ -0,0 +1,37 @@ + + + PopulateConstantConfig + + + + 0 + 0 + 400 + 77 + + + + Form + + + + + + Constant value: + + + + + + PopulateConstant.Value + + + + + + + + + + + diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp new file mode 100644 index 0000000..3d78de5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp @@ -0,0 +1,94 @@ +#include "populatedictionary.h" +#include "services/populatemanager.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include +#include +#include + +PopulateDictionary::PopulateDictionary() +{ +} + +QString PopulateDictionary::getTitle() const +{ + return tr("Dictionary", "dictionary populating plugin name"); +} + +PopulateEngine*PopulateDictionary::createEngine() +{ + return new PopulateDictionaryEngine(); +} + +bool PopulateDictionaryEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + QFile file(cfg.PopulateDictionary.File.get()); + if (!file.open(QIODevice::ReadOnly)) + { + notifyError(QObject::tr("Could not open dictionary file %1 for reading.").arg(cfg.PopulateDictionary.File.get())); + return false; + } + QTextStream stream(&file); + QString dataStr = stream.readAll(); + file.close(); + + if (cfg.PopulateDictionary.Lines.get()) + dictionary = dataStr.split("\n"); + else + dictionary = dataStr.split(QRegExp("\\s+")); + + if (dictionary.size() == 0) + dictionary << QString(); + + dictionaryPos = 0; + dictionarySize = dictionary.size(); + if (cfg.PopulateDictionary.Random.get()) + qsrand(QDateTime::currentDateTime().toTime_t()); + + return true; +} + +QVariant PopulateDictionaryEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + if (cfg.PopulateDictionary.Random.get()) + { + int r = qrand() % dictionarySize; + return dictionary[r]; + } + else + { + if (dictionaryPos >= dictionarySize) + dictionaryPos = 0; + + return dictionary[dictionaryPos++]; + } +} + +void PopulateDictionaryEngine::afterPopulating() +{ + dictionary.clear(); + dictionarySize = 0; + dictionaryPos = 0; +} + +CfgMain* PopulateDictionaryEngine::getConfig() +{ + return &cfg; +} + +QString PopulateDictionaryEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateDictionaryConfig"); +} + +bool PopulateDictionaryEngine::validateOptions() +{ + QFileInfo fi(cfg.PopulateDictionary.File.get()); + bool fileValid = fi.exists() && fi.isReadable() && !fi.isDir(); + POPULATE_MANAGER->handleValidationFromPlugin(fileValid, cfg.PopulateDictionary.File, QObject::tr("Dictionary file must exist and be readable.")); + + return fileValid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h new file mode 100644 index 0000000..f5e754f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h @@ -0,0 +1,52 @@ +#ifndef POPULATEDICTIONARY_H +#define POPULATEDICTIONARY_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +class QFile; +class QTextStream; + +CFG_CATEGORIES(PopulateDictionaryConfig, + CFG_CATEGORY(PopulateDictionary, + CFG_ENTRY(QString, File, QString()) + CFG_ENTRY(bool, Lines, false) + CFG_ENTRY(bool, Random, false) + ) +) + +class PopulateDictionary : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Dictionary") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with values from a dictionary file.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateDictionary(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateDictionaryEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateDictionaryConfig, cfg) + QStringList dictionary; + int dictionarySize = 0; + int dictionaryPos = 0; +}; + +#endif // POPULATEDICTIONARY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui new file mode 100644 index 0000000..f99491f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui @@ -0,0 +1,123 @@ + + + PopulateDictionaryConfig + + + + 0 + 0 + 307 + 255 + + + + Form + + + + + + Dictionary file + + + + + + PopulateDictionary.File + + + Pick dictionary file + + + + + + + + + + Word separator + + + + + + PopulateDictionary.Lines + + + Whitespace + + + false + + + + + + + PopulateDictionary.Lines + + + Line break + + + true + + + + + + + + + + Method of using words + + + + + + PopulateDictionary.Random + + + Ordered + + + false + + + + + + + PopulateDictionary.Random + + + Randomly + + + true + + + + + + + + + + + ConfigRadioButton + QRadioButton +
common/configradiobutton.h
+
+ + FileEdit + QWidget +
common/fileedit.h
+ 1 +
+
+ + +
diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h new file mode 100644 index 0000000..1a1db43 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h @@ -0,0 +1,70 @@ +#ifndef POPULATEPLUGIN_H +#define POPULATEPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugins/plugin.h" + +class CfgMain; +class PopulateEngine; +class Db; + +class API_EXPORT PopulatePlugin : virtual public Plugin +{ + public: + virtual PopulateEngine* createEngine() = 0; +}; + +class API_EXPORT PopulateEngine +{ + public: + virtual ~PopulateEngine() {} + + virtual bool beforePopulating(Db* db, const QString& table) = 0; + virtual QVariant nextValue(bool& nextValueError) = 0; + virtual void afterPopulating() = 0; + + /** + * @brief Provides config object that holds configuration for populating. + * @return Config object, or null if the importing with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of this plugin in the populate dialog. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If populating with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getPopulateConfigFormName() const = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * @return true if the validation was successful, or false otherwise. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling POPULATE_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + * + * In this method you can also call POPULATE_MANAGER->configStateUpdateFromPlugin() to adjust options UI + * to the current config values. + * + * Apart from calling POPULATE_MANAGER with validation results, it should also return true or false, + * according to validation results. The return value is used by the PopulateDialog to tell if the plugin + * is currently configured correctly, without going into details, without handling signals from POPULATE_MANAGER. + */ + virtual bool validateOptions() = 0; +}; + + +#endif // POPULATEPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp new file mode 100644 index 0000000..3258bbc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp @@ -0,0 +1,55 @@ +#include "populaterandom.h" +#include "services/populatemanager.h" +#include "common/unused.h" +#include + +PopulateRandom::PopulateRandom() +{ +} + +QString PopulateRandom::getTitle() const +{ + return tr("Random number"); +} + +PopulateEngine* PopulateRandom::createEngine() +{ + return new PopulateRandomEngine(); +} + +bool PopulateRandomEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + qsrand(QDateTime::currentDateTime().toTime_t()); + range = cfg.PopulateRandom.MaxValue.get() - cfg.PopulateRandom.MinValue.get() + 1; + return (range > 0); +} + +QVariant PopulateRandomEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + QString randValue = QString::number((qrand() % range) + cfg.PopulateRandom.MinValue.get()); + return (cfg.PopulateRandom.Prefix.get() + randValue + cfg.PopulateRandom.Suffix.get()); +} + +void PopulateRandomEngine::afterPopulating() +{ +} + +CfgMain* PopulateRandomEngine::getConfig() +{ + return &cfg; +} + +QString PopulateRandomEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateRandomConfig"); +} + +bool PopulateRandomEngine::validateOptions() +{ + bool valid = (cfg.PopulateRandom.MinValue.get() <= cfg.PopulateRandom.MaxValue.get()); + POPULATE_MANAGER->handleValidationFromPlugin(valid, cfg.PopulateRandom.MaxValue, QObject::tr("Maximum value cannot be less than minimum value.")); + return valid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h new file mode 100644 index 0000000..f4e9feb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h @@ -0,0 +1,47 @@ +#ifndef POPULATERANDOM_H +#define POPULATERANDOM_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateRandomConfig, + CFG_CATEGORY(PopulateRandom, + CFG_ENTRY(int, MinValue, 0) + CFG_ENTRY(int, MaxValue, 99999999) + CFG_ENTRY(QString, Prefix, QString()) + CFG_ENTRY(QString, Suffix, QString()) + ) +) + +class PopulateRandom : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Random") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with random numbers.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateRandom(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateRandomEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateRandomConfig, cfg) + int range; +}; +#endif // POPULATERANDOM_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui new file mode 100644 index 0000000..fb304ea --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui @@ -0,0 +1,106 @@ + + + PopulateRandomConfig + + + + 0 + 0 + 400 + 144 + + + + Form + + + + + + Constant prefix + + + + + + PopulateRandom.Prefix + + + No prefix + + + + + + + + + + Minimum value + + + + + + PopulateRandom.MinValue + + + -999999999 + + + 999999999 + + + + + + + + + + Maximum value + + + + + + PopulateRandom.MaxValue + + + -999999999 + + + 999999999 + + + 999999999 + + + + + + + + + + Constant suffix + + + + + + PopulateRandom.Suffix + + + No suffix + + + + + + + + + + + diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp new file mode 100644 index 0000000..d9f148a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp @@ -0,0 +1,91 @@ +#include "populaterandomtext.h" +#include "common/utils.h" +#include "common/unused.h" +#include "services/populatemanager.h" + +PopulateRandomText::PopulateRandomText() +{ +} + +QString PopulateRandomText::getTitle() const +{ + return tr("Random text"); +} + +PopulateEngine* PopulateRandomText::createEngine() +{ + return new PopulateRandomTextEngine(); +} + +bool PopulateRandomTextEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + qsrand(QDateTime::currentDateTime().toTime_t()); + range = cfg.PopulateRandomText.MaxLength.get() - cfg.PopulateRandomText.MinLength.get() + 1; + + chars = ""; + + if (cfg.PopulateRandomText.UseCustomSets.get()) + { + chars = cfg.PopulateRandomText.CustomCharacters.get(); + } + else if (cfg.PopulateRandomText.IncludeBinary.get()) + { + for (int i = 0; i < 256; i++) + chars.append(QChar((char)i)); + } + else + { + if (cfg.PopulateRandomText.IncludeAlpha.get()) + chars += QStringLiteral("abcdefghijklmnopqrstuvwxyz"); + + if (cfg.PopulateRandomText.IncludeNumeric.get()) + chars += QStringLiteral("0123456789"); + + if (cfg.PopulateRandomText.IncludeWhitespace.get()) + chars += QStringLiteral(" \t\n"); + } + + return !chars.isEmpty(); +} + +QVariant PopulateRandomTextEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + int lgt = (qrand() % range) + cfg.PopulateRandomText.MinLength.get(); + return randStr(lgt, chars); +} + +void PopulateRandomTextEngine::afterPopulating() +{ +} + +CfgMain* PopulateRandomTextEngine::getConfig() +{ + return &cfg; +} + +QString PopulateRandomTextEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateRandomTextConfig"); +} + +bool PopulateRandomTextEngine::validateOptions() +{ + bool rangeValid = (cfg.PopulateRandomText.MinLength.get() <= cfg.PopulateRandomText.MaxLength.get()); + POPULATE_MANAGER->handleValidationFromPlugin(rangeValid, cfg.PopulateRandomText.MaxLength, QObject::tr("Maximum length cannot be less than minimum length.")); + + bool useCustom = cfg.PopulateRandomText.UseCustomSets.get(); + bool useBinary = cfg.PopulateRandomText.IncludeBinary.get(); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeAlpha, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeNumeric, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeWhitespace, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeBinary, true, !useCustom); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.CustomCharacters, true, useCustom); + + bool customValid = !useCustom || !cfg.PopulateRandomText.CustomCharacters.get().isEmpty(); + POPULATE_MANAGER->handleValidationFromPlugin(customValid, cfg.PopulateRandomText.CustomCharacters, QObject::tr("Custom character set cannot be empty.")); + + return rangeValid && customValid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h new file mode 100644 index 0000000..892b302 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h @@ -0,0 +1,52 @@ +#ifndef POPULATERANDOMTEXT_H +#define POPULATERANDOMTEXT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateRandomTextConfig, + CFG_CATEGORY(PopulateRandomText, + CFG_ENTRY(int, MinLength, 4) + CFG_ENTRY(int, MaxLength, 20) + CFG_ENTRY(bool, IncludeAlpha, true) + CFG_ENTRY(bool, IncludeNumeric, true) + CFG_ENTRY(bool, IncludeWhitespace, true) + CFG_ENTRY(bool, IncludeBinary, false) + CFG_ENTRY(bool, UseCustomSets, false) + CFG_ENTRY(QString, CustomCharacters, QString()) + ) +) +class PopulateRandomText : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Random text") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with random characters.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateRandomText(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateRandomTextEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateRandomTextConfig, cfg) + int range; + QString chars; +}; + +#endif // POPULATERANDOMTEXT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui new file mode 100644 index 0000000..28febde --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui @@ -0,0 +1,181 @@ + + + PopulateRandomTextConfig + + + + 0 + 0 + 367 + 291 + + + + Form + + + + + + PopulateRandomText.UseCustomSets + + + Use characters from common sets: + + + true + + + false + + + + + + + Minimum length + + + + + + PopulateRandomText.MinLength + + + 999999999 + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Letters from a to z. + + + PopulateRandomText.IncludeAlpha + + + Alpha + + + + + + + Numbers from 0 to 9. + + + PopulateRandomText.IncludeNumeric + + + Numeric + + + + + + + A whitespace, a tab and a new line character. + + + PopulateRandomText.IncludeWhitespace + + + Whitespace + + + + + + + Includes all above and all others. + + + PopulateRandomText.IncludeBinary + + + Binary + + + + + + + + + + PopulateRandomText.UseCustomSets + + + Use characters from my custom set: + + + true + + + + + + + Maximum length + + + + + + PopulateRandomText.MaxLength + + + 999999999 + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + If you type some character multiple times, it's more likely to be used. + + + PopulateRandomText.CustomCharacters + + + + + + + + + + + ConfigRadioButton + QRadioButton +
common/configradiobutton.h
+
+
+ + +
diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp new file mode 100644 index 0000000..79a8ac1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp @@ -0,0 +1,125 @@ +#include "populatescript.h" +#include "common/unused.h" +#include "services/populatemanager.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" + +PopulateScript::PopulateScript() +{ +} + +QString PopulateScript::getTitle() const +{ + return tr("Script"); +} + +PopulateEngine* PopulateScript::createEngine() +{ + return new PopulateScriptEngine(); +} + +bool PopulateScriptEngine::beforePopulating(Db* db, const QString& table) +{ + this->db = db; + this->table = table; + + evalArgs = {db->getName(), table}; + + scriptingPlugin = nullptr; + for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins()) + { + if (plugin->getLanguage() == cfg.PopulateScript.Language.get()) + { + scriptingPlugin = plugin; + break; + } + } + + if (!scriptingPlugin) + { + notifyError(QObject::tr("Could not find plugin to support scripting language: %1").arg(cfg.PopulateScript.Language.get())); + return false; + } + + dbAwarePlugin = dynamic_cast(scriptingPlugin); + + context = scriptingPlugin->createContext(); + + QString initCode = cfg.PopulateScript.InitCode.get(); + if (!initCode.trimmed().isEmpty()) + { + if (dbAwarePlugin) + dbAwarePlugin->evaluate(context, initCode, evalArgs, db); + else + scriptingPlugin->evaluate(context, initCode, evalArgs); + + if (scriptingPlugin->hasError(context)) + { + notifyError(QObject::tr("Error while executing populating initial code: %1").arg(scriptingPlugin->getErrorMessage(context))); + releaseContext(); + return false; + } + } + + rowCnt = 1; + evalArgs << rowCnt; + + return true; +} + +QVariant PopulateScriptEngine::nextValue(bool& nextValueError) +{ + QVariant result; + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(context, cfg.PopulateScript.Code.get(), evalArgs, db); + else + result = scriptingPlugin->evaluate(context, cfg.PopulateScript.Code.get(), evalArgs); + + if (scriptingPlugin->hasError(context)) + { + notifyError(QObject::tr("Error while executing populating code: %1").arg(scriptingPlugin->getErrorMessage(context))); + releaseContext(); + nextValueError = true; + return QVariant(); + } + + evalArgs[2] = ++rowCnt; + + return result; +} + +void PopulateScriptEngine::afterPopulating() +{ + releaseContext(); +} + +CfgMain* PopulateScriptEngine::getConfig() +{ + return &cfg; +} + +QString PopulateScriptEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateScriptConfig"); +} + +bool PopulateScriptEngine::validateOptions() +{ + bool langValid = !cfg.PopulateScript.Language.get().isEmpty(); + bool codeValid = !cfg.PopulateScript.Code.get().trimmed().isEmpty(); + QString lang = cfg.PopulateScript.Language.get(); + + POPULATE_MANAGER->handleValidationFromPlugin(langValid, cfg.PopulateScript.Language, QObject::tr("Select implementation language.")); + POPULATE_MANAGER->handleValidationFromPlugin(codeValid, cfg.PopulateScript.Code, QObject::tr("Implementation code cannot be empty.")); + + POPULATE_MANAGER->propertySetFromPlugin(cfg.PopulateScript.InitCode, PluginServiceBase::LANG_PROPERTY_NAME, lang); + POPULATE_MANAGER->propertySetFromPlugin(cfg.PopulateScript.Code, PluginServiceBase::LANG_PROPERTY_NAME, lang); + + return langValid && codeValid; +} + +void PopulateScriptEngine::releaseContext() +{ + scriptingPlugin->releaseContext(context); + context = nullptr; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h new file mode 100644 index 0000000..8870b67 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h @@ -0,0 +1,63 @@ +#ifndef POPULATESCRIPT_H +#define POPULATESCRIPT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" +#include "scriptingplugin.h" + +CFG_CATEGORIES(PopulateScriptConfig, + CFG_CATEGORY(PopulateScript, + CFG_ENTRY(QString, Language, QString()) + CFG_ENTRY(QString, InitCode, QString()) + CFG_ENTRY(QString, Code, QString()) + ) +) + +/** + * @brief Populate from evaluated script code + * + * Initial code evaluation gets 2 arguments - the db name and the table name. + * Each evaluation of per-step code gets 3 arguments, 2 of them just like above + * and the 3rd is number of the row being currently populated (starting from 1). + */ +class PopulateScript : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Constant") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with a constant value.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateScript(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateScriptEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateScriptConfig, cfg) + void releaseContext(); + + ScriptingPlugin* scriptingPlugin = nullptr; + DbAwareScriptingPlugin* dbAwarePlugin = nullptr; + ScriptingPlugin::Context* context = nullptr; + Db* db = nullptr; + QString table; + int rowCnt = 0; + QList evalArgs; +}; + +#endif // POPULATESCRIPT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui new file mode 100644 index 0000000..8d37994 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui @@ -0,0 +1,112 @@ + + + PopulateScriptConfig + + + + 0 + 0 + 443 + 362 + + + + Form + + + + 440 + 360 + + + + + + + Initialization code (optional) + + + + + + PopulateScript.InitCode + + + true + + + + + + + + + + Per step code + + + + + + PopulateScript.Code + + + true + + + + + + + + + + + 0 + 0 + + + + Language + + + + + + PopulateScript.Language + + + true + + + + + + + + + + Help + + + + + + ... + + + http://sqlitestudio.pl/wiki/index.php/Official_plugins#Script_.28built-in.29 + + + help + + + + + + + + + + + diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp new file mode 100644 index 0000000..a0ad94e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp @@ -0,0 +1,53 @@ +#include "populatesequence.h" +#include "common/global.h" +#include "services/populatemanager.h" +#include "common/unused.h" +#include + +PopulateSequence::PopulateSequence() +{ +} + +QString PopulateSequence::getTitle() const +{ + return tr("Sequence"); +} + +PopulateEngine* PopulateSequence::createEngine() +{ + return new PopulateSequenceEngine(); +} + +bool PopulateSequenceEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + seq = cfg.PopulateSequence.StartValue.get(); + step = cfg.PopulateSequence.Step.get(); + return true; +} + +QVariant PopulateSequenceEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + return seq += step; +} + +void PopulateSequenceEngine::afterPopulating() +{ +} + +CfgMain* PopulateSequenceEngine::getConfig() +{ + return &cfg; +} + +QString PopulateSequenceEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateSequenceConfig"); +} + +bool PopulateSequenceEngine::validateOptions() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h new file mode 100644 index 0000000..5435477 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h @@ -0,0 +1,47 @@ +#ifndef POPULATESEQUENCE_H +#define POPULATESEQUENCE_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateSequenceConfig, + CFG_CATEGORY(PopulateSequence, + CFG_ENTRY(int, StartValue, 0) + CFG_ENTRY(int, Step, 1) + ) +) + +class PopulateSequence : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Sequence") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with sequenced values.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateSequence(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateSequenceEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateSequenceConfig, cfg) + qint64 seq = 0; + qint64 step = 1; +}; + +#endif // POPULATESEQUENCE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui new file mode 100644 index 0000000..231af85 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui @@ -0,0 +1,64 @@ + + + PopulateSequenceConfig + + + + 0 + 0 + 279 + 67 + + + + Form + + + + + + PopulateSequence.StartValue + + + -99999999 + + + 99999999 + + + + + + + Start value: + + + + + + + PopulateSequence.Step + + + -999999 + + + 999999 + + + 1 + + + + + + + Step: + + + + + + + + diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h new file mode 100644 index 0000000..d081073 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h @@ -0,0 +1,50 @@ +#ifndef SCRIPTINGPLUGIN_H +#define SCRIPTINGPLUGIN_H + +#include "plugin.h" +#include + +class Db; + +class ScriptingPlugin : virtual public Plugin +{ + public: + class Context + { + public: + virtual ~Context() {} + }; + + virtual QString getLanguage() const = 0; + virtual Context* createContext() = 0; + virtual void releaseContext(Context* context) = 0; + virtual void resetContext(Context* context) = 0; + virtual void setVariable(Context* context, const QString& name, const QVariant& value) = 0; + virtual QVariant getVariable(Context* context, const QString& name) = 0; + virtual QVariant evaluate(Context* context, const QString& code, const QList& args = QList()) = 0; + virtual bool hasError(Context* context) const = 0; + virtual QString getErrorMessage(Context* context) const = 0; + virtual QVariant evaluate(const QString& code, const QList& args = QList(), QString* errorMessage = nullptr) = 0; + virtual QString getIconPath() const = 0; +}; + +class DbAwareScriptingPlugin : public ScriptingPlugin +{ + public: + virtual QVariant evaluate(Context* context, const QString& code, const QList& args, Db* db, bool locking = false) = 0; + virtual QVariant evaluate(const QString& code, const QList& args, Db* db, bool locking = false, QString* errorMessage = nullptr) = 0; + + QVariant evaluate(Context* context, const QString& code, const QList& args) + { + return evaluate(context, code, args, nullptr, true); + } + + QVariant evaluate(const QString& code, const QList& args, QString* errorMessage = nullptr) + { + return evaluate(code, args, nullptr, true, errorMessage); + } +}; + +Q_DECLARE_METATYPE(ScriptingPlugin::Context*) + +#endif // SCRIPTINGPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp new file mode 100644 index 0000000..334c0cc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp @@ -0,0 +1,276 @@ +#include "scriptingqt.h" +#include "common/unused.h" +#include "common/global.h" +#include "scriptingqtdbproxy.h" +#include +#include +#include +#include + +static QScriptValue scriptingQtDebug(QScriptContext *context, QScriptEngine *engine) +{ + UNUSED(engine); + QStringList args; + for (int i = 0; i < context->argumentCount(); i++) + args << context->argument(i).toString(); + + qDebug() << "[ScriptingQt]" << args; + return QScriptValue(); +} + +ScriptingQt::ScriptingQt() +{ + mainEngineMutex = new QMutex(); +} + +ScriptingQt::~ScriptingQt() +{ + safe_delete(mainEngineMutex); +} + +QString ScriptingQt::getLanguage() const +{ + return QStringLiteral("QtScript"); +} + +ScriptingPlugin::Context* ScriptingQt::createContext() +{ + ContextQt* ctx = new ContextQt; + ctx->engine->pushContext(); + contexts << ctx; + return ctx; +} + +void ScriptingQt::releaseContext(ScriptingPlugin::Context* context) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + contexts.removeOne(ctx); + delete ctx; +} + +void ScriptingQt::resetContext(ScriptingPlugin::Context* context) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + ctx->engine->popContext(); + ctx->engine->pushContext(); +} + +QVariant ScriptingQt::evaluate(const QString& code, const QList& args, Db* db, bool locking, QString* errorMessage) +{ + QMutexLocker locker(mainEngineMutex); + + // Enter a new context + QScriptContext* engineContext = mainContext->engine->pushContext(); + + // Call the function + QVariant result = evaluate(mainContext, engineContext, code, args, db, locking); + + // Handle errors + if (!mainContext->error.isEmpty()) + *errorMessage = mainContext->error; + + // Leave the context to reset "this". + mainContext->engine->popContext(); + + return result; +} + +QVariant ScriptingQt::evaluate(ScriptingPlugin::Context* context, const QString& code, const QList& args, Db* db, bool locking) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QVariant(); + + return evaluate(ctx, ctx->engine->currentContext(), code, args, db, locking); +} + +QVariant ScriptingQt::evaluate(ContextQt* ctx, QScriptContext* engineContext, const QString& code, const QList& args, Db* db, bool locking) +{ + // Define function to call + QScriptValue functionValue = getFunctionValue(ctx, code); + + // Db for this evaluation + ctx->dbProxy->setDb(db); + ctx->dbProxy->setUseDbLocking(locking); + + // Call the function + QScriptValue result; + if (args.size() > 0) + result = functionValue.call(engineContext->activationObject(), ctx->engine->toScriptValue(args)); + else + result = functionValue.call(engineContext->activationObject()); + + // Handle errors + ctx->error.clear(); + if (ctx->engine->hasUncaughtException()) + ctx->error = ctx->engine->uncaughtException().toString(); + + ctx->dbProxy->setDb(nullptr); + ctx->dbProxy->setUseDbLocking(false); + + return convertVariant(result.toVariant()); +} + +QVariant ScriptingQt::convertVariant(const QVariant& value, bool wrapStrings) +{ + switch (value.type()) + { + case QVariant::Hash: + { + QHash hash = value.toHash(); + QHashIterator it(hash); + QStringList list; + while (it.hasNext()) + { + it.next(); + list << it.key() + ": " + convertVariant(it.value(), true).toString(); + } + return "{" + list.join(", ") + "}"; + } + case QVariant::Map: + { + QMap map = value.toMap(); + QMapIterator it(map); + QStringList list; + while (it.hasNext()) + { + it.next(); + list << it.key() + ": " + convertVariant(it.value(), true).toString(); + } + return "{" + list.join(", ") + "}"; + } + case QVariant::List: + { + QStringList list; + for (const QVariant& var : value.toList()) + list << convertVariant(var, true).toString(); + + return "[" + list.join(", ") + "]"; + } + case QVariant::StringList: + { + return "[\"" + value.toStringList().join("\", \"") + "\"]"; + } + case QVariant::String: + { + if (wrapStrings) + return "\"" + value.toString() + "\""; + + break; + } + default: + break; + } + return value; +} + +void ScriptingQt::setVariable(ScriptingPlugin::Context* context, const QString& name, const QVariant& value) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + ctx->engine->globalObject().setProperty(name, ctx->engine->newVariant(value)); +} + +QVariant ScriptingQt::getVariable(ScriptingPlugin::Context* context, const QString& name) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QVariant(); + + QScriptValue value = ctx->engine->globalObject().property(name); + return convertVariant(value.toVariant()); +} + +bool ScriptingQt::hasError(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return false; + + return !ctx->error.isEmpty(); +} + +QString ScriptingQt::getErrorMessage(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QString::null; + + return ctx->error; +} + +QString ScriptingQt::getIconPath() const +{ + return ":/images/plugins/scriptingqt.png"; +} + +bool ScriptingQt::init() +{ + QMutexLocker locker(mainEngineMutex); + mainContext = new ContextQt; + return true; +} + +void ScriptingQt::deinit() +{ + foreach (Context* ctx, contexts) + delete ctx; + + contexts.clear(); + + QMutexLocker locker(mainEngineMutex); + safe_delete(mainContext); +} + +ScriptingQt::ContextQt* ScriptingQt::getContext(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = dynamic_cast(context); + if (!ctx) + qDebug() << "Invalid context passed to ScriptingQt:" << context; + + return ctx; +} + +QScriptValue ScriptingQt::getFunctionValue(ContextQt* ctx, const QString& code) +{ + static const QString fnDef = QStringLiteral("(function () {%1\n})"); + + QScriptProgram* prog = nullptr; + if (!ctx->scriptCache.contains(code)) + { + prog = new QScriptProgram(fnDef.arg(code)); + ctx->scriptCache.insert(code, prog); + } + else + { + prog = ctx->scriptCache[code]; + } + return ctx->engine->evaluate(*prog); +} + +ScriptingQt::ContextQt::ContextQt() +{ + engine = new QScriptEngine(); + + dbProxy = new ScriptingQtDbProxy(); + dbProxyScriptValue = engine->newQObject(dbProxy, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater); + + engine->globalObject().setProperty("debug", engine->newFunction(scriptingQtDebug)); + engine->globalObject().setProperty("db", dbProxyScriptValue); + + scriptCache.setMaxCost(cacheSize); +} + +ScriptingQt::ContextQt::~ContextQt() +{ + safe_delete(engine); + safe_delete(dbProxy); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h new file mode 100644 index 0000000..125789a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h @@ -0,0 +1,72 @@ +#ifndef SCRIPTINGQT_H +#define SCRIPTINGQT_H + +#include "builtinplugin.h" +#include "scriptingplugin.h" +#include +#include +#include +#include +#include + +class QScriptEngine; +class QMutex; +class QScriptContext; +class ScriptingQtDbProxy; + +class ScriptingQt : public BuiltInPlugin, public DbAwareScriptingPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Qt scripting") + SQLITESTUDIO_PLUGIN_DESC("Qt scripting support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + ScriptingQt(); + ~ScriptingQt(); + + QString getLanguage() const; + Context* createContext(); + void releaseContext(Context* context); + void resetContext(Context* context); + QVariant evaluate(const QString& code, const QList& args, Db* db, bool locking = false, QString* errorMessage = nullptr); + QVariant evaluate(Context* context, const QString& code, const QList& args, Db* db, bool locking = false); + void setVariable(Context* context, const QString& name, const QVariant& value); + QVariant getVariable(Context* context, const QString& name); + bool hasError(Context* context) const; + QString getErrorMessage(Context* context) const; + QString getIconPath() const; + bool init(); + void deinit(); + + private: + using DbAwareScriptingPlugin::evaluate; + + class ContextQt : public ScriptingPlugin::Context + { + public: + ContextQt(); + ~ContextQt(); + + QScriptEngine* engine = nullptr; + QCache scriptCache; + QString error; + ScriptingQtDbProxy* dbProxy = nullptr; + QScriptValue dbProxyScriptValue; + }; + + ContextQt* getContext(ScriptingPlugin::Context* context) const; + QScriptValue getFunctionValue(ContextQt* ctx, const QString& code); + QVariant evaluate(ContextQt* ctx, QScriptContext* engineContext, const QString& code, const QList& args, Db* db, bool locking); + QVariant convertVariant(const QVariant& value, bool wrapStrings = false); + + static const constexpr int cacheSize = 5; + + ContextQt* mainContext = nullptr; + QList contexts; + QMutex* mainEngineMutex = nullptr; +}; + +#endif // SCRIPTINGQT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png new file mode 100644 index 0000000..220ea27 Binary files /dev/null and b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png differ diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp new file mode 100644 index 0000000..ff3c7ee --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp @@ -0,0 +1,145 @@ +#include "scriptingqtdbproxy.h" +#include "db/db.h" +#include "db/sqlquery.h" +#include +#include + +ScriptingQtDbProxy::ScriptingQtDbProxy(QObject *parent) : + QObject(parent) +{ +} +Db* ScriptingQtDbProxy::getDb() const +{ + return db; +} + +void ScriptingQtDbProxy::setDb(Db* value) +{ + db = value; +} +bool ScriptingQtDbProxy::getUseDbLocking() const +{ + return useDbLocking; +} + +void ScriptingQtDbProxy::setUseDbLocking(bool value) +{ + useDbLocking = value; +} + +QHash ScriptingQtDbProxy::mapToHash(const QMap& map) +{ + QHash hash; + QMapIterator it(map); + while (it.hasNext()) + { + it.next(); + hash[it.key()] = it.value(); + } + return hash; +} + +QVariant ScriptingQtDbProxy::evalInternal(const QString& sql, const QList& listArgs, const QMap& mapArgs, + bool singleCell, const QScriptValue* funcPtr) +{ + if (!db) + { + QString funcName = singleCell ? QStringLiteral("db.onecolumn()") : QStringLiteral("db.eval()"); + context()->throwError(tr("No database available in current context, while called QtScript's %1 command.").arg(funcName)); + return evalInternalErrorResult(singleCell); + } + + Db::Flags flags; + if (!useDbLocking) + flags |= Db::Flag::NO_LOCK; + + SqlQueryPtr results; + if (listArgs.size() > 0) + results = db->exec(sql, listArgs, flags); + else + results = db->exec(sql, mapToHash(mapArgs), flags); + + if (results->isError()) + { + QString funcName = singleCell ? QStringLiteral("db.onecolumn()") : QStringLiteral("db.eval()"); + context()->throwError(tr("Error from %1: %2").arg(funcName, results->getErrorText())); + return evalInternalErrorResult(singleCell); + } + + if (singleCell) + { + return results->getSingleCell(); + } + else if (funcPtr) + { + QScriptValue func(*funcPtr); + SqlResultsRowPtr row; + QScriptValue funcArgs; + QScriptValue funcResult; + while (results->hasNext()) + { + row = results->next(); + funcArgs = context()->engine()->toScriptValue(row->valueList()); + funcResult = func.call(context()->thisObject(), funcArgs); + if (!funcResult.isUndefined()) + break; + } + return funcResult.toVariant(); + } + else + { + QList evalResults; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + evalResults << QVariant(row->valueList()); + } + return evalResults; + } +} + +QVariant ScriptingQtDbProxy::evalInternalErrorResult(bool singleCell) +{ + QList result; + if (singleCell) + result << QVariant(); + + return result; +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql) +{ + return evalInternal(sql, QList(), QMap(), false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QList& args) +{ + return evalInternal(sql, args, QMap(), false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QMap& args) +{ + return evalInternal(sql, QList(), args, false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QList& args, const QScriptValue& func) +{ + return evalInternal(sql, args, QMap(), false, &func); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QMap& args, const QScriptValue& func) +{ + return evalInternal(sql, QList(), args, false, &func); +} + +QVariant ScriptingQtDbProxy::onecolumn(const QString& sql, const QList& args) +{ + return evalInternal(sql, args, QMap(), true); +} + +QVariant ScriptingQtDbProxy::onecolumn(const QString& sql, const QMap& args) +{ + return evalInternal(sql, QList(), args, true); +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h new file mode 100644 index 0000000..add9540 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h @@ -0,0 +1,44 @@ +#ifndef SCRIPTINGQTDBPROXY_H +#define SCRIPTINGQTDBPROXY_H + +#include +#include +#include +#include +#include + +class Db; + +class ScriptingQtDbProxy : public QObject, protected QScriptable +{ + Q_OBJECT + public: + explicit ScriptingQtDbProxy(QObject *parent = 0); + + Db* getDb() const; + void setDb(Db* value); + + bool getUseDbLocking() const; + void setUseDbLocking(bool value); + + private: + QVariant evalInternal(const QString& sql, const QList& listArgs, const QMap& mapArgs, bool singleCell, + const QScriptValue* funcPtr = nullptr); + QVariant evalInternalErrorResult(bool singleCell); + + static QHash mapToHash(const QMap& map); + + Db* db = nullptr; + bool useDbLocking = false; + + public slots: + QVariant eval(const QString& sql); + QVariant eval(const QString& sql, const QList& args); + QVariant eval(const QString& sql, const QMap& args); + QVariant eval(const QString& sql, const QList& args, const QScriptValue& func); + QVariant eval(const QString& sql, const QMap& args, const QScriptValue& func); + QVariant onecolumn(const QString& sql, const QList& args); + QVariant onecolumn(const QString& sql, const QMap& args); +}; + +#endif // SCRIPTINGQTDBPROXY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp new file mode 100644 index 0000000..93a6d91 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp @@ -0,0 +1,146 @@ +#include "scriptingsql.h" +#include "common/unused.h" +#include "db/db.h" +#include "db/sqlquery.h" +#include "services/dbmanager.h" + +ScriptingSql::ScriptingSql() +{ +} + +ScriptingSql::~ScriptingSql() +{ +} + +QString ScriptingSql::getLanguage() const +{ + return "SQL"; +} + +ScriptingPlugin::Context* ScriptingSql::createContext() +{ + SqlContext* ctx = new SqlContext(); + contexts << ctx; + return ctx; +} + +void ScriptingSql::releaseContext(ScriptingPlugin::Context* context) +{ + if (!contexts.contains(context)) + return; + + delete context; + contexts.removeOne(context); +} + +void ScriptingSql::resetContext(ScriptingPlugin::Context* context) +{ + dynamic_cast(context)->errorText.clear(); +} + +QVariant ScriptingSql::evaluate(ScriptingPlugin::Context* context, const QString& code, const QList& args, Db* db, bool locking) +{ + SqlContext* ctx = dynamic_cast(context); + ctx->errorText.clear(); + + Db* theDb = nullptr; + if (db && db->isValid()) + theDb = db; + else if (memDb) + theDb = memDb; + else + return QVariant(); + + Db::Flags execFlags; + if (!locking) + execFlags |= Db::Flag::NO_LOCK; + + QString sql = code; + if (ctx->variables.size() > 0) + { + QString value; + for (const QString& key : ctx->variables.keys()) + { + value = "'" + ctx->variables[key].toString() + "'"; + sql.replace(":" + key, value).replace("@" + key, value).replace("$" + key, value); + } + } + + SqlQueryPtr result = theDb->exec(sql, args, execFlags); + if (result->isError()) + { + dynamic_cast(context)->errorText = result->getErrorText(); + return QVariant(); + } + + return result->getSingleCell(); +} + +QVariant ScriptingSql::evaluate(const QString& code, const QList& args, Db* db, bool locking, QString* errorMessage) +{ + Db* theDb = nullptr; + + if (db && db->isValid()) + theDb = db; + else if (memDb) + theDb = memDb; + else + return QVariant(); + + Db::Flags execFlags; + if (!locking) + execFlags |= Db::Flag::NO_LOCK; + + SqlQueryPtr result = theDb->exec(code, args, execFlags); + if (result->isError()) + { + *errorMessage = result->getErrorText(); + return QVariant(); + } + + return result->getSingleCell(); +} + +void ScriptingSql::setVariable(ScriptingPlugin::Context* context, const QString& name, const QVariant& value) +{ + dynamic_cast(context)->variables[name] = value; +} + +QVariant ScriptingSql::getVariable(ScriptingPlugin::Context* context, const QString& name) +{ + if (dynamic_cast(context)->variables.contains(name)) + return dynamic_cast(context)->variables[name]; + + return QVariant(); +} + +bool ScriptingSql::hasError(ScriptingPlugin::Context* context) const +{ + return !getErrorMessage(context).isNull(); +} + +QString ScriptingSql::getErrorMessage(ScriptingPlugin::Context* context) const +{ + return dynamic_cast(context)->errorText; +} + +QString ScriptingSql::getIconPath() const +{ + return ":/images/plugins/scriptingsql.png"; +} + +bool ScriptingSql::init() +{ + memDb = DBLIST->createInMemDb(); + return memDb != nullptr; +} + +void ScriptingSql::deinit() +{ + for (Context* context : contexts) + delete context; + + contexts.clear(); + + safe_delete(memDb); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h new file mode 100644 index 0000000..7b8fd3b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h @@ -0,0 +1,46 @@ +#ifndef SCRIPTINGSQL_H +#define SCRIPTINGSQL_H + +#include "builtinplugin.h" +#include "scriptingplugin.h" + +class ScriptingSql : public BuiltInPlugin, public DbAwareScriptingPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("SQL scripting") + SQLITESTUDIO_PLUGIN_DESC("SQL scripting support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + class SqlContext : public Context + { + public: + QString errorText; + QHash variables; + }; + + ScriptingSql(); + ~ScriptingSql(); + + QString getLanguage() const; + Context* createContext(); + void releaseContext(Context* context); + void resetContext(Context* context); + QVariant evaluate(Context* context, const QString& code, const QList& args, Db* db, bool locking); + QVariant evaluate(const QString& code, const QList& args, Db* db, bool locking, QString* errorMessage); + void setVariable(Context* context, const QString& name, const QVariant& value); + QVariant getVariable(Context* context, const QString& name); + bool hasError(Context* context) const; + QString getErrorMessage(Context* context) const; + QString getIconPath() const; + bool init(); + void deinit(); + + private: + QList contexts; + Db* memDb = nullptr; +}; + +#endif // SCRIPTINGSQL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png new file mode 100644 index 0000000..ea232a7 Binary files /dev/null and b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png differ diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp new file mode 100644 index 0000000..5e1d610 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp @@ -0,0 +1,29 @@ +#include "sqlformatterplugin.h" +#include "parser/parser.h" +#include "db/db.h" +#include + +QString SqlFormatterPlugin::format(const QString& code, Db* contextDb) +{ + Dialect dialect = Dialect::Sqlite3; + if (contextDb && contextDb->isValid()) + contextDb->getDialect(); + + Parser parser(dialect); + if (!parser.parse(code)) + { + qWarning() << "Could not parse SQL in order to format it. The SQL was:" << code; + return code; + } + + QStringList formattedQueries; + foreach (SqliteQueryPtr query, parser.getQueries()) + formattedQueries << format(query); + + return formattedQueries.join("\n"); +} + +QString SqlFormatterPlugin::getLanguage() const +{ + return "sql"; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h new file mode 100644 index 0000000..cb500f6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h @@ -0,0 +1,16 @@ +#ifndef SQLFORMATTERPLUGIN_H +#define SQLFORMATTERPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "codeformatterplugin.h" +#include "parser/ast/sqlitequery.h" + +class API_EXPORT SqlFormatterPlugin : public CodeFormatterPlugin +{ + public: + QString format(const QString& code, Db* contextDb); + QString getLanguage() const; + virtual QString format(SqliteQueryPtr query) = 0; +}; + +#endif // SQLFORMATTERPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h new file mode 100644 index 0000000..c9af8e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h @@ -0,0 +1,56 @@ +#ifndef UICONFIGUREDPLUGIN_H +#define UICONFIGUREDPLUGIN_H + +#include "coreSQLiteStudio_global.h" + +class API_EXPORT UiConfiguredPlugin +{ + public: + /** + * @brief Gets name of the configuration UI form. + * @return Name of the form object. + * + * Some plugins may link (during compilation) only to the coreSQLiteStudio part of the application, but they can still + * benefit from SQLiteStudio GUI application by providing UI form that will be used in ConfigDialog. + * + * This method should return the object name of the top-most widget found in the provided *.ui file. + * + * For more details see: http://wiki.sqlitestudio.pl/index.php/Plugin_UI_forms + */ + virtual QString getConfigUiForm() const = 0; + + /** + * @brief Provides config object for ConfigDialog. + * @return Config used by the plugin, or null when there's no config, or when config should not be configured with property binding. + * + * When this method returns null, but getConfigUiForm() returns existing form, then configuration is assumed to be kept + * in global CfgMain object (which is known to application, as all global CfgMain objects). In this case configuration is loaded/stored + * using initial and final calls to load/store values from the form. This is different from when this method returns not null. Keep reading. + * + * When this method returns pointer to an object, then ConfigDialog uses ConfigMapper to bind widgets from getConfigUiForm() with + * config values from CfgMain returned by this method. See ConfigMapper for details about binding. + * In this case ConfigDialog uses CfgMain::begin(), CfgMain::commit() and CfgMain::rollback() methods to make changes to the config + * transactional (when users clicks "cancel" or "apply"). + */ + virtual CfgMain* getMainUiConfig() = 0; + + /** + * @brief Notifies about ConfigDialog being just open. + * + * This is called just after the config dialog was open and all its contents are already initialized. + * This is a good moment to connect to plugin's CfgMain configuration object to listen for changes, + * so all uncommited (yet) configuration changes can be reflected by this plugin. + */ + virtual void configDialogOpen() = 0; + + /** + * @brief Notifies about ConfigDialog being closed. + * + * This is called just before the config dialog gets closed. + * This is a good moment to disconnect from configuration object and not listen to changes in the configuration anymore + * (couse config can change for example when application is starting and loading entire configuration, etc). + */ + virtual void configDialogClosed() = 0; +}; + +#endif // UICONFIGUREDPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.cpp b/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.cpp new file mode 100644 index 0000000..85d1da8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.cpp @@ -0,0 +1,21 @@ +#include "pluginservicebase.h" + +PluginServiceBase::PluginServiceBase(QObject *parent) : + QObject(parent) +{ +} + +void PluginServiceBase::handleValidationFromPlugin(bool configValid, CfgEntry* key, const QString& errorMessage) +{ + emit validationResultFromPlugin(configValid, key, errorMessage); +} + +void PluginServiceBase::propertySetFromPlugin(CfgEntry* key, const QString& propertyName, const QVariant& value) +{ + emit widgetPropertyFromPlugin(key, propertyName, value); +} + +void PluginServiceBase::updateVisibilityAndEnabled(CfgEntry* key, bool visible, bool enabled) +{ + emit stateUpdateRequestFromPlugin(key, visible, enabled); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h b/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h new file mode 100644 index 0000000..56bb7a2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h @@ -0,0 +1,95 @@ +#ifndef PLUGINSERVICEBASE_H +#define PLUGINSERVICEBASE_H + +#include "common/global.h" +#include "coreSQLiteStudio_global.h" +#include + +class CfgEntry; + +class API_EXPORT PluginServiceBase : public QObject +{ + Q_OBJECT + + public: + /** + * @brief Name of property to store scripting language. + * + * This property is used by plugins to store scripting language associated with given widget. + * Upon update of this property, the higlighter can be dynamically changed. + * Having this in a dynamic property we can keep plugins independent from UI, but they still + * can interact with the UI. + */ + static_char* LANG_PROPERTY_NAME = "language"; + + explicit PluginServiceBase(QObject *parent = 0); + + /** + * @brief Available for the plugins to report validation errors on their UI forms. + * @param configValid If the config value is valid or not. + * @param key The config key that was validated. + * @param errorMessage if the \p valid is false, then the \p errorMessage can carry the details of the validation result. + * + * Since import plugins themself are independet from QtGui, they still can provide *.ui files + * and they can use CFG_CATEGORIES to bind with *.ui files, then they can validate values + * stored in the CFG_CATEGORIES. In case that some value is invalid, they should call + * this method to let the UI know, that the widget should be marked for invalid value. + */ + void handleValidationFromPlugin(bool configValid, CfgEntry* key, const QString& errorMessage = QString()); + + /** + * @brief Available for the plugins to set custom properties on their UI forms. + * @param key The config key that the property reffers to (it must be bind to the UI widget). + * @param propertyName Name of the property to set. + * @param value Value for the property. + * + * This method is here for similar purpose as handleValidationFromPlugin(), just handles different action from the plugin. + */ + void propertySetFromPlugin(CfgEntry* key, const QString& propertyName, const QVariant& value); + + /** + * @brief Available for the plugins to update UI of their options accordingly to the config values. + * @param key The config key that the update is about. + * @param visible The visibility for the widget. + * @param enabled Enabled/disabled state for the widget. + * + * This method is here for the same reason that the handleValidationFromPlugin() is. + */ + void updateVisibilityAndEnabled(CfgEntry* key, bool visible, bool enabled); + + signals: + /** + * @brief Emitted when the plugin performed its configuration validation. + * @param valid true if plugin accepts its configuration. + * @param key a key that cause valid/invalid state. + * @param errorMessage if the \p valid is false, then the \p errorMessage can carry the details of the validation result. + * + * Slot handling this signal should update UI to reflect the configuration state. + */ + void validationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMessage); + + /** + * @brief Emitted when plugin wants to set custom property value for the UI widget. + * @param key a key that cause valid/invalid state. + * @param propertyName Name of the property to set. + * @param value Value for the property. + * + * Slot handling this signal should set the property to the widget which is bind to the given key. + */ + void widgetPropertyFromPlugin(CfgEntry* key, const QString& propertyName, const QVariant& value); + + /** + * @brief Emitted when the plugin wants to update UI according to config values. + * @param key The config key that the update is about. + * @param visible The visibility for the widget. + * @param enabled Enabled/disabled state for the widget. + * + * Slot handling this signal should update UI to reflect the state provided in parameters. + */ + void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled); + + public slots: + +}; + +#endif // PLUGINSERVICEBASE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp b/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp new file mode 100644 index 0000000..71ab9a4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp @@ -0,0 +1,110 @@ +#include "populateworker.h" +#include "common/utils_sql.h" +#include "db/db.h" +#include "db/sqlquery.h" +#include "plugins/populateplugin.h" +#include "services/notifymanager.h" + +PopulateWorker::PopulateWorker(Db* db, const QString& table, const QStringList& columns, const QList& engines, qint64 rows, QObject* parent) : + QObject(parent), db(db), table(table), columns(columns), engines(engines), rows(rows) +{ +} + +PopulateWorker::~PopulateWorker() +{ +} + +void PopulateWorker::run() +{ + static const QString insertSql = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3);"); + + if (!db->begin()) + { + notifyError(tr("Could not start transaction in order to perform table populating. Error details: %1").arg(db->getErrorText())); + emit finished(false); + return; + } + + Dialect dialect = db->getDialect(); + QString wrappedTable = wrapObjIfNeeded(table, dialect); + + QStringList cols; + QStringList argList; + for (const QString& column : columns) + { + cols << wrapObjIfNeeded(column, dialect); + argList << "?"; + } + + QString finalSql = insertSql.arg(wrappedTable, cols.join(", "), argList.join(", ")); + SqlQueryPtr query = db->prepare(finalSql); + + QList args; + bool nextValueError = false; + for (qint64 i = 0; i < rows; i++) + { + if (i == 0 && !beforePopulating()) + return; + + args.clear(); + for (PopulateEngine* engine : engines) + { + args << engine->nextValue(nextValueError); + db->rollback(); + emit finished(false); + return; + } + + query->setArgs(args); + if (!query->execute()) + { + notifyError(tr("Error while populating table: %1").arg(query->getErrorText())); + db->rollback(); + emit finished(false); + return; + } + } + + if (!db->commit()) + { + notifyError(tr("Could not commit transaction after table populating. Error details: %1").arg(db->getErrorText())); + db->rollback(); + emit finished(false); + return; + } + + afterPopulating(); + emit finished(true); +} + +bool PopulateWorker::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} + +bool PopulateWorker::beforePopulating() +{ + for (PopulateEngine* engine : engines) + { + if (!engine->beforePopulating(db, table)) + { + db->rollback(); + emit finished(false); + return false; + } + } + return true; +} + +void PopulateWorker::afterPopulating() +{ + for (PopulateEngine* engine : engines) + engine->afterPopulating(); +} + +void PopulateWorker::interrupt() +{ + QMutexLocker locker(&interruptMutex); + interrupted = true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/populateworker.h b/SQLiteStudio3/coreSQLiteStudio/populateworker.h new file mode 100644 index 0000000..f0a39f8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/populateworker.h @@ -0,0 +1,41 @@ +#ifndef POPULATEWORKER_H +#define POPULATEWORKER_H + +#include +#include +#include +#include + +class Db; +class PopulateEngine; + +class PopulateWorker : public QObject, public QRunnable +{ + Q_OBJECT + public: + explicit PopulateWorker(Db* db, const QString& table, const QStringList& columns, const QList& engines, qint64 rows, QObject *parent = 0); + ~PopulateWorker(); + + void run(); + + private: + bool isInterrupted(); + bool beforePopulating(); + void afterPopulating(); + + Db* db = nullptr; + QString table; + QStringList columns; + QList engines; + qint64 rows; + bool interrupted = false; + QMutex interruptMutex; + + public slots: + void interrupt(); + + signals: + void finished(bool result); +}; + +#endif // POPULATEWORKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/qio.cpp b/SQLiteStudio3/coreSQLiteStudio/qio.cpp new file mode 100644 index 0000000..24f95fe --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/qio.cpp @@ -0,0 +1,5 @@ +#include "qio.h" + +QTextStream qOut(stdout); +QTextStream qIn(stdin); +QTextStream qErr(stderr); diff --git a/SQLiteStudio3/coreSQLiteStudio/qio.h b/SQLiteStudio3/coreSQLiteStudio/qio.h new file mode 100644 index 0000000..f97962c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/qio.h @@ -0,0 +1,33 @@ +#ifndef QIO_H +#define QIO_H + +#include "coreSQLiteStudio_global.h" +#include + +/** @file */ + +/** + * @brief Standard output stream. + * + * This is pretty much the same as std::cout, except it's based on QTextStream, + * therefore accepts larger range of data types. + */ +API_EXPORT extern QTextStream qOut; + +/** + * @brief Standard input stream. + * + * This is pretty much the same as std::cin, except it's based on QTextStream, + * therefore accepts larger range of data types. + */ +API_EXPORT extern QTextStream qIn; + +/** + * @brief Standard error stream. + * + * This is pretty much the same as std::cerr, except it's based on QTextStream, + * therefore accepts larger range of data types. + */ +API_EXPORT extern QTextStream qErr; + +#endif // QIO_H diff --git a/SQLiteStudio3/coreSQLiteStudio/querymodel.cpp b/SQLiteStudio3/coreSQLiteStudio/querymodel.cpp new file mode 100644 index 0000000..8d20b70 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/querymodel.cpp @@ -0,0 +1,63 @@ +#include "querymodel.h" +#include "db/db.h" +#include "common/unused.h" + +QueryModel::QueryModel(Db* db, QObject *parent) : + QAbstractTableModel(parent), db(db) +{ +} + +void QueryModel::refresh() +{ + if (!db || !db->isOpen()) + return; + + beginResetModel(); + loadedRows.clear(); + SqlQueryPtr results = db->exec(query); + for (SqlResultsRowPtr row : results->getAll()) + loadedRows += row; + + columns = results->columnCount(); + endResetModel(); + + emit refreshed(); +} + +QVariant QueryModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + int rowIdx = index.row(); + if (rowIdx < loadedRows.size()) + return loadedRows[rowIdx]->value(index.column()); + + return QVariant(); +} + +int QueryModel::rowCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return loadedRows.size(); +} + +int QueryModel::columnCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return columns; +} + +QString QueryModel::getQuery() const +{ + return query; +} + +void QueryModel::setQuery(const QString& value) +{ + query = value; + refresh(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/querymodel.h b/SQLiteStudio3/coreSQLiteStudio/querymodel.h new file mode 100644 index 0000000..6c7e48a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/querymodel.h @@ -0,0 +1,42 @@ +#ifndef SQLQUERYMODEL_H +#define SQLQUERYMODEL_H + +#include "db/sqlquery.h" +#include "db/sqlresultsrow.h" +#include +#include + +class Db; + +class API_EXPORT QueryModel : public QAbstractTableModel +{ + Q_OBJECT + + public: + using QAbstractItemModel::fetchMore; + using QAbstractItemModel::canFetchMore; + + QueryModel(Db* db, QObject *parent = nullptr); + + virtual void refresh(); + QVariant data(const QModelIndex& index, int role) const; + int rowCount(const QModelIndex& parent) const; + int columnCount(const QModelIndex& parent) const; + + QString getQuery() const; + void setQuery(const QString& value); + + private: + void fetchMore(); + bool canFetchMore() const; + + QString query; + Db* db = nullptr; + QList loadedRows; + int columns = 0; + + signals: + void refreshed(); +}; + +#endif // SQLQUERYMODEL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/returncode.cpp b/SQLiteStudio3/coreSQLiteStudio/returncode.cpp new file mode 100644 index 0000000..80edbe4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/returncode.cpp @@ -0,0 +1,51 @@ +#include "returncode.h" + +ReturnCode::ReturnCode() + : retCode(0) +{ +} + +ReturnCode::ReturnCode(QString errorMessage) + : retCode(0) +{ + errorMessages << errorMessage; +} + +ReturnCode::ReturnCode(quint16 code, QString errorMessage) + : retCode(code) +{ + errorMessages << errorMessage; +} + +void ReturnCode::addMessage(QString errorMessage) +{ + errorMessages << errorMessage; +} + +bool ReturnCode::isOk() +{ + return retCode != 0; +} + +QList &ReturnCode::getMessages() +{ + return errorMessages; +} + +QString ReturnCode::message() +{ + if (errorMessages.size() > 0) + return errorMessages.at(0); + else + return QString::null; +} + +void ReturnCode::setCode(quint16 code) +{ + retCode = code; +} + +quint16 ReturnCode::code() +{ + return retCode; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/returncode.h b/SQLiteStudio3/coreSQLiteStudio/returncode.h new file mode 100644 index 0000000..71af885 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/returncode.h @@ -0,0 +1,26 @@ +#ifndef RETURNCODE_H +#define RETURNCODE_H + +#include +#include + +class ReturnCode +{ + private: + quint16 retCode; + QList errorMessages; + + public: + ReturnCode(); + explicit ReturnCode(QString errorMessage); + ReturnCode(quint16 code, QString errorMessage); + + void addMessage(QString errorMessage); + bool isOk(); + QList& getMessages(); + QString message(); + void setCode(quint16 code); + quint16 code(); +}; + +#endif // RETURNCODE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp new file mode 100644 index 0000000..0579add --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp @@ -0,0 +1,1151 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * BigInt.cpp + * + * Author: Nedim Srndic + * Release date: 14th of March 2008 + * + * This file contains the implementation for the BigInt class. + * + * There are two static constants defined in this file: + * + * - ULongMax : ULONG_MAX (defined in climits) of type BigInt + * Mainly used for speedup in the multiply() private member function. + * Represents the largest unsigned long integer a particular platform can + * handle. If a BigInt is <= ULongMax, it can be converted to unsigned + * long int. This is platform-specific. + * - SqrtUlongMax : sqrt(ULONG_MAX) of type BigInt + * Mainly used for speedup in the multiply() private member function. + * Represents the square root of the largest unsigned long integer a + * particular platform can handle. If two BigInts are <= SqrtULongMax, + * they can be converted to unsigned long int and safely multiplied + * by the CPU. This is platform-specific. + * + * **************************************************************************** + */ + +//comment the following line if you want to use long multiplication +//#define KARATSUBA + +#include "BigInt.h" +#include //strlen() +#include //ULONG_MAX +#include //vector +#include //operator std::string() +#include //reverse_copy(), copy(), copy_backward(), + //fill(), fill_n() + +using std::cout; +using std::endl; + +//define and initialize BigInt::FACTOR +const double BigInt::FACTOR = 1.6; + +//A BigInt number with the value of ULONG_MAX +static const BigInt ULongMax(ULONG_MAX); +//A BigInt number with the value of sqrt(ULONG_MAX) +static const BigInt SqrtULongMax + (static_cast(sqrt(static_cast(ULONG_MAX)))); + +/* Transforms the number from unsigned long int to unsigned char[] + * and pads the result with zeroes. Returns the number of digits. */ +unsigned long int BigInt::int2uchar(unsigned long int number, + unsigned char *digits, + unsigned long int padding = 0L) +{ + int i(0); + do + { + //the number is stored in reverse + //(i.e. long int 456 is stored as unsigned char[] {[6][5][4]}) + digits[i++] = (unsigned char) (number % 10); + number /= 10; + } while (number > 0L); + + std::fill_n(digits + i, padding, 0); + return i; +} + +/* Converts ASCII digits to equivalent unsigned char numeric values. */ +void BigInt::char2uchar(unsigned char *array, + unsigned long int length) +{ + for (unsigned long int i(0L); i < length; i++) + array[i] -= '0'; +} + +/* Check if all ASCII values are digits '0' to '9'. */ +bool BigInt::allCharsAreDigits( const char *array, + unsigned long int length) +{ + for (unsigned long int i(0L); i < length; i++) + if (array[i] < '0' || array[i] > '9') + return false; + + return true; +} + +/* Compares two BigInt. If the last two arguments are + * omitted, the comparison is sign-insensitive (comparison by + * absolute value). Returns 0 if a == b, 1 if a > b, 2 if a < b. */ +int BigInt::compareNumbers( unsigned char *a, unsigned long int na, + unsigned char *b, unsigned long int nb, + bool aPositive, bool bPositive) +{ + if (na < nb || (!aPositive && bPositive)) + //a < b + return 2; + else if (na > nb || (aPositive && !bPositive)) + //a > b + return 1; + + //check the digits one by one starting from the most significant one + for (long int i = na - 1; i >= 0L; i--) + //compare the digits + if (a[i] != b[i]) + { + if (a[i] < b[i]) // |a| < |b| + if (aPositive) + return 2; // a < b + else + return 1; // a > b + else // |a| > |b| + if (aPositive) + return 1; // a > b + else + return 2; // a < b + } + + //a == b + return 0; +} + +/* Multiplies two unsigned char[] using the Divide and Conquer + * a.k.a. Karatsuba algorithm .*/ +void BigInt::karatsubaMultiply( unsigned char *a, unsigned char *b, + unsigned long int n, unsigned char *buf1) +{ + //if *a <= SqrtULongMax && *b <= SqrtULongMax, + //the CPU can do the multiplication + if (compareNumbers(a, n, SqrtULongMax.digits, SqrtULongMax.digitCount) != 1 + && + compareNumbers(b, n, SqrtULongMax.digits, SqrtULongMax.digitCount) != 1 + ) + { + int2uchar(toInt(a, n) * toInt(b, n), buf1, n << 1); + return; + } + + //nh = higher half digits, nl = lower half digits + //nh == nl || nh + 1 == nl + //nt is used to avoid too much nl + 1 addition operations + unsigned long int nh(n >> 1), nl(n - nh), nt(nl + 1); + //t1 is a temporary pointer, points to p1 + unsigned char *t1(buf1 + (n << 1)); + + BigInt::add(a + nl, nh, a, nl, buf1, nt); + BigInt::add(b + nl, nh, b, nl, buf1 + nt, nt); + BigInt::karatsubaMultiply(a + nl, b + nl, nh, t1); //p1 + BigInt::karatsubaMultiply(a, b, nl, t1 + (nh << 1)); //p2 + BigInt::karatsubaMultiply(buf1, buf1 + nt, nt, t1 + (n << 1));//p3 + + //for leftshifting p3 and p1 + unsigned long int power(n); + if (power & 1) + power++; + //since the original multiplier is not needed any more, we can reuse a + a = buf1 + (power >> 1); + //copy and shift left p3 by power / 2 and pad right to n * 2 with zeroes + std::fill(buf1, a, 0); + std::copy(t1 + (n << 1), t1 + ((n + nl) << 1) + 1, a); + std::fill(a + (nl << 1) + 1, t1, 0); + + //shifted p3 -= p2 + //a = shifted p3, b = p2 + BigInt::quickSub(a, t1 + (nh << 1), t1, nl); + + //shifted p3 -= p1 + //a = shifted p3, b = p1 + BigInt::quickSub(a, t1, t1, nh); + + //shifted p3 += shifted p1 + //a = p3[power], b = p1 + a = buf1 + power; + BigInt::quickAdd(a, t1, nh); + + //p3 += p2 + //a = p3, b = p2 + unsigned char carry = BigInt::quickAdd(buf1, t1 + (nh << 1), nl); + a = buf1 + (nl << 1); + for (unsigned long int i(0L); carry; i++) + { + a[i] += 1; + carry = a[i] / 10; + a[i] %= 10; + } +} + +/* Multiplies two unsigned char[] the long way. */ +void BigInt::longMultiply( unsigned char *a, unsigned long int na, + unsigned char *b, unsigned long int nb, + unsigned char *result) +{ + std::fill_n(result, na + nb, 0); + unsigned char mult(0); + int carry(0); + + for (unsigned long int i(0L); i < na; i++) + { + for (unsigned long int j(0L); j < nb; j++) + { + mult = a[i] * b[j] + result[i + j] + carry; + result[i + j] = static_cast(mult) % 10; + carry = static_cast(mult) / 10; + } + if (carry) + { + result[i + nb] += carry; + carry = 0; + } + } +} + +/* Simple addition, used by the multiply function. + * Returns the remaining carry. */ +unsigned char BigInt::quickAdd( unsigned char *a, unsigned char *b, + unsigned long int n) +{ + unsigned char carry(0), sum(0); + for (unsigned long int i(0L); i < (n << 1); i++) + { + sum = a[i] + b[i] + carry; + carry = sum / 10; + a[i] = sum % 10; + } + return carry; +} + +/* Simple subtraction, used by the multiply function. */ +void BigInt::quickSub( unsigned char *a, unsigned char *b, + unsigned char *end, unsigned long int n) +{ + unsigned char carry(0), sum(0); + for (unsigned long int i(0L); i < (n << 1); i++) + { + sum = 10 + a[i] - (b[i] + carry); + if (sum < 10) //carry + { + a[i] = sum; + carry = 1; + } + else + { + a[i] = sum % 10; + carry = 0; + } + } + a = &a[n << 1]; + for (; carry && a < end; a++) + if (*a) + { + (*a)--; + break; + } + else + *a = 9; +} + +/* Divides two BigInt numbers by the formula + * dividend = divisor * quotient + remainder*/ +void BigInt::divide(const BigInt ÷nd, const BigInt &divisor, + BigInt "ient, BigInt &remainder) +{ + BigInt Z1, R, X(dividend.Abs()); + /* Make sure quotient and remainder are zero. + * The lack of this assignment introduces a bug if the actual parameters + * are not zero when calling this function. */ + quotient = BigIntZero; + remainder = BigIntZero; + + // while |X| >= |divisor| + while (BigInt::compareNumbers( X.digits, X.digitCount, + divisor.digits, divisor.digitCount, + true, true) != 2) + { + unsigned long int O(X.digitCount - divisor.digitCount); + if (O <= ULongMax.digitCount - 2) + { + unsigned long int i; + if (X.digitCount > ULongMax.digitCount - 1) + i = ULongMax.digitCount - 1; + else + i = X.digitCount; + unsigned long int j(i - O); + Z1 = toInt(X.digits + X.digitCount - i, i) / + toInt(divisor.digits + divisor.digitCount - j, j); + } + else + { + unsigned long int i(ULongMax.digitCount - 1); + unsigned long int j; + if (divisor.digitCount > ULongMax.digitCount - 2) + j = ULongMax.digitCount - 2; + else + j = divisor.digitCount; + Z1 = toInt(X.digits + X.digitCount - i, i) / + toInt(divisor.digits + divisor.digitCount - j, j); + Z1.shiftLeft(O - Z1.digitCount); + } + + predictZ1: + R = (Z1 * divisor).Abs(); + + if (X >= R) + { + X = X - R; + quotient += Z1; + } + else + { + if (Z1.digitCount > 1) + Z1.shiftRight(1); + else + --Z1; + goto predictZ1; + } + } + + remainder = X; +} + +/* Returns the value of the specified unsigned char[] as long int. */ +unsigned long int BigInt::toInt(unsigned char *digits, int n) +{ + unsigned long int newInt(0L); + unsigned long int powerOf10(1); + for (int i(0); i < n; i++) + { + newInt += digits[i] * powerOf10; + powerOf10 *= 10; + } + return newInt; +} + +/* Saves the sum of two unsigned char* shorter and longer into result. + * It must be nShorter <= nLonger. If doFill == true, it fills the + * remaining free places with zeroes (used in KaratsubaMultiply()). + * Returns true if there was an overflow at the end (meaning that + * the result.digitCount was longer.digitCount + 1. */ +bool BigInt::add(unsigned char *shorter, unsigned long int nShorter, + unsigned char *longer, unsigned long int nLonger, + unsigned char *result, int nResult, bool doFill) +{ + //single digitwise sum and carry + unsigned char subSum(0); + unsigned char subCarry(0); + + //count the digits + unsigned long int i(0L); + + //add the digits + for (; i < nShorter; i++) + { + subSum = longer[i] + shorter[i] + subCarry; + subCarry = subSum / 10; + result[i] = subSum % 10; + } + + for (; i < nLonger; i++) + { + subSum = longer[i] + subCarry; + subCarry = subSum / 10; + result[i] = subSum % 10; + } + + if (doFill) + std::fill_n(result + i, nResult - i, 0); + + if (subCarry) + { + result[i++] = 1; + return true; + } + return false; +} + +/* Shifts the digits n places left. */ +BigInt &BigInt::shiftLeft(unsigned long int n) +{ + //if the number is 0, we won't shift it + if (EqualsZero()) + return *this; + if (length <= digitCount + n + 2) + expandTo(digitCount + n + 2); + + std::copy_backward(digits, digits + digitCount, digits + n + digitCount); + std::fill_n(digits, n, 0); + digitCount += n; + return *this; +} + +/* Shifts the digits n places right. */ +BigInt &BigInt::shiftRight(unsigned long int n) +{ + if (n >= digitCount) + throw "Error BIGINT00: Overflow on shift right."; + + std::copy_backward( digits + n, digits + digitCount, + digits + digitCount - n); + digitCount -= n; + return *this; +} + +/* Expands the digits* to n. */ +void BigInt::expandTo(unsigned long int n) +{ + unsigned long int oldLength(length); + length = n; + unsigned char *oldDigits(digits); + try + { + digits = new unsigned char[length]; + } + catch (...) + { + delete[] digits; + digits = oldDigits; + length = oldLength; + throw "Error BIGINT01: BigInt creation error (out of memory?)."; + } + + std::copy(oldDigits, oldDigits + digitCount, digits); + delete[] oldDigits; +} + +BigInt::BigInt() : digits(0), length(10), digitCount(1), positive(true) +{ + try + { + digits = new unsigned char[length]; + } + catch (...) + { + delete[] digits; + throw "Error BIGINT02: BigInt creation error (out of memory?)."; + } + + //initialize to 0 + digits[0] = 0; +} + +BigInt::BigInt(const char * charNum) : digits(0) +{ + digitCount = (unsigned long int) strlen(charNum); + + if (digitCount == 0L) + throw "Error BIGINT03: Input string empty."; + else + { + switch (charNum[0]) + { + case '+': + digitCount--; + charNum++; + positive = true; + break; + case '-': + digitCount--; + charNum++; + positive = false; + break; + default: + positive = true; + } + } + + //get rid of the leading zeroes + while (charNum[0] == '0') + { + charNum++; + digitCount --; + } + + //check if the string contains only decimal digits + if (! BigInt::allCharsAreDigits(charNum, digitCount)) + throw "Error BIGINT04: Input string contains characters" + " other than digits."; + + //the input string was like ('+' or '-')"00...00\0" + if (charNum[0] == '\0') + { + digitCount = 1; + charNum--; + positive = true; + } + + length = (unsigned long int)(digitCount * BigInt::FACTOR + 1); + + try + { + digits = new unsigned char[length]; + } + catch (...) + { + delete[] digits; + throw "Error BIGINT05: BigInt creation error (out of memory?)."; + } + + //copy the digits backwards to the new BigInt + std::reverse_copy(charNum, charNum + digitCount, digits); + //convert them to unsigned char + BigInt::char2uchar(digits, digitCount); +} + +BigInt::BigInt(unsigned long int intNum) : digits(0) +{ + positive = true; + + //we don't know how many digits there are in intNum since + //sizeof(long int) is platform dependent (2^128 ~ 39 digits), so we'll + //first save them in a temporary unsigned char[], and later copy them + unsigned char tempDigits[40] = {0}; + + digitCount = int2uchar(intNum, tempDigits); + length = (unsigned long int)(digitCount * BigInt::FACTOR + 1); + + try + { + digits = new unsigned char[length]; + } + catch (...) + { + delete [] digits; + throw "Error BIGINT06: BigInt creation error (out of memory?)."; + } + + std::copy(tempDigits, tempDigits + digitCount, digits); +} + +BigInt::BigInt(const std::string &str) : digits(0), length(10), + digitCount(1), positive(true) +{ + try + { + digits = new unsigned char[length]; + } + catch (...) + { + delete[] digits; + throw "Error BIGINT07: BigInt creation error (out of memory?)."; + } + + //initialize to 0 + digits[0] = 0; + BigInt a(str.c_str()); + *this = a; +} + +BigInt::BigInt(const BigInt &rightNumber) : length(rightNumber.length), +digitCount(rightNumber.digitCount), positive(rightNumber.positive) +{ + //make sure we have just enough space + if (length <= digitCount + 2 || length > (digitCount << 2)) + length = (unsigned long int) (digitCount * BigInt::FACTOR + 1); + try + { + digits = new unsigned char[length]; + } + catch (...) + { + delete[] digits; + throw "Error BIGINT08: BigInt creation error (out of memory?)."; + } + + std::copy(rightNumber.digits, rightNumber.digits + digitCount, digits); +} + +BigInt::operator std::string() const +{ + return ToString(); +} + +BigInt &BigInt::operator =(const BigInt &rightNumber) +{ + //if the right-hand operand is longer than the left-hand one or + //twice as small + if (length < rightNumber.digitCount + 2 || + length > (rightNumber.digitCount << 2)) + { + length = (unsigned long int) + (rightNumber.digitCount * BigInt::FACTOR + 1); + //keep a pointer to the current digits, in case + //there is not enough memory to allocate for the new digits + unsigned char *tempDigits(digits); + + try + { + digits = new unsigned char[length]; + } + catch (...) + { + //clean up the mess + delete[] digits; + //restore the digits + digits = tempDigits; + throw "Error BIGINT09: BigInt assignment error (out of memory?)."; + } + //it turns out we don't need this any more + delete[] tempDigits; + } + //destructive self-assignment protection + else if (this == &rightNumber) + return *this; + + //copy the values + digitCount = rightNumber.digitCount; + positive = rightNumber.positive; + std::copy(rightNumber.digits, rightNumber.digits + digitCount, digits); + return *this; +} + +std::ostream &operator <<(std::ostream &cout, const BigInt &number) +{ + if (!number.positive) + cout << '-'; + for (int i = number.digitCount - 1; i >= 0; i--) + cout << (int(number.digits[i])); + + return cout; +} + +std::istream &operator >>(std::istream &cin, BigInt &number) +{ + std::string newNumber; + std::cin >> std::ws >> newNumber; + if (!cin) + { + cin.clear(); + throw "Error BIGINT16: Input stream error."; + } + + number = newNumber; + return cin; +} + +bool operator <(const BigInt &a, const BigInt &b) +{ + if (BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount, + a.positive, b.positive) == 2) + return true; + return false; +} + +bool operator <=(const BigInt &a, const BigInt &b) +{ + if (BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount, + a.positive, b.positive) == 1) + return false; + return true; +} + +bool operator >(const BigInt &a, const BigInt &b) +{ + if (BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount, + a.positive, b.positive) == 1) + return true; + return false; +} + +bool operator >=(const BigInt &a, const BigInt &b) +{ + if (BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount, + a.positive, b.positive) == 2) + return false; + return true; +} + +bool operator ==(const BigInt &a, const BigInt &b) +{ + if (BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount, + a.positive, b.positive)) + return false; + return true; +} + +bool operator !=(const BigInt &a, const BigInt &b) +{ + if (BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount, + a.positive, b.positive)) + return true; + return false; +} + +BigInt operator +(const BigInt &a, const BigInt &b) +{ + if (a.positive && !b.positive) + return a - (-b); + else if (!a.positive && b.positive) + return b - (-a); + + //find the longer of the operands + const BigInt *shorter, *longer; + if (BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount) == 1) + { + shorter = &b; + longer = &a; + } + else + { + shorter = &a; + longer = &b; + } + + //Copies the "positive" field too. That is good because now either a and b + //are both positive or both negative, so the result has the same sign. + BigInt sum(*longer); + + bool overflow = BigInt::add(shorter->digits, shorter->digitCount, + longer->digits, longer->digitCount, + sum.digits, 0, false); + if (overflow) + sum.digitCount++; + + return sum; +} + +/*overloaded ++ operator, prefix version*/ +BigInt &BigInt::operator++() +{ + return *this += BigIntOne; +} + +/*overloaded ++ operator, postfix version*/ +BigInt BigInt::operator++(int) +{ + BigInt temp(*this); + *this += BigIntOne; + return temp; +} + +BigInt &BigInt::operator+=(const BigInt &number) +{ + *this = *this + number; + return *this; +} + +BigInt BigInt::operator-() const +{ + if (!this->EqualsZero()) + { + BigInt temp(*this); + temp.positive = !temp.positive; + return temp; + } + return *this; +} + +BigInt operator-(const BigInt &a, const BigInt &b) +{ + if (!a.positive && b.positive) + { + return -((-a) + b); + } + if (a.positive && !b.positive) + { + return a + (-b); + } + + const int cmpAbs = BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount); + //if a == b + if ((cmpAbs == 0) && (a.positive == b.positive)) + { + return BigIntZero; + } + + //find the longer of the operands (bigger by absolute value) + const BigInt *shorter, *longer; + bool sign(a.positive); //the sign of the result + if (cmpAbs != 2) // a >= b + { + shorter = &b; + longer = &a; + } + else + { + shorter = &a; + longer = &b; + sign = !sign; + } + + BigInt result(*longer); + result.positive = sign; + //temporary variable + const BigInt shorterCopy(*shorter); + //often used temporary variable + const int rDigits(shorterCopy.digitCount); + //in case of longer digitwise carry, overflow = true + bool overflow(false); + + for (int i(0); i < rDigits; i++) + { + overflow = (longer->digits[i] - shorterCopy.digits[i]) < 0; + if (overflow) + { + result.digits[i] = longer->digits[i] + 10 - shorterCopy.digits[i]; + //transfer carry + shorterCopy.digits[i+1]++; + } + else + //make the digitwise subtraction + result.digits[i] = longer->digits[i] - shorterCopy.digits[i]; + } + + //if there is a carry and the following digit is 0 => there will + //be a carry again... + if (overflow && result.digits[rDigits] == 0) + { + result.digits[rDigits] = 9; + + int i(rDigits + 1); + for (; result.digits[i] == 0; i++) + result.digits[i] = 9; + + result.digits[i] -= 1; + } //there is a carry but there will be no more carries + else if (overflow) + result.digits[rDigits]--; + + //get rid of the leading zeroes + for (int i(result.digitCount - 1); i > 0; i--) + if (result.digits[i] == 0) + result.digitCount--; + else + break; + + return result; +} + +/*overloaded -- operator, prefix version*/ +BigInt &BigInt::operator--() +{ + *this = *this - BigIntOne; + return *this; +} + +/*overloaded -- operator, postfix version*/ +BigInt BigInt::operator--(int) +{ + BigInt temp(*this); + *this = *this - BigIntOne; + return temp; +} + +BigInt &BigInt::operator-=(const BigInt &number) +{ + *this = *this - number; + return *this; +} + +BigInt operator*(const BigInt &a, const BigInt &b) +{ + if (a.EqualsZero() || b.EqualsZero()) + return BigIntZero; + + //this controls wether Karatsuba algorithm will be used for multiplication +#ifdef KARATSUBA + int n((a.digitCount < b.digitCount ? b.digitCount : a.digitCount)); + + //we will use a temporary buffer for multiplication + unsigned char *buffer(0); + + try + { + buffer = new unsigned char[11 * n]; + } + catch (...) + { + delete[] buffer; + throw "Error BIGINT10: Not enough memory?"; + } + + unsigned char *bb(buffer + n), *bc(bb + n); + + std::copy(a.digits, a.digits + a.digitCount, buffer); + std::fill(buffer + a.digitCount, buffer + n, 0); + std::copy(b.digits, b.digits + b.digitCount, bb); + std::fill(bb + b.digitCount, bb + n, 0); + + BigInt::karatsubaMultiply(buffer, bb, n, bc); + + n <<= 1; +#else + int n = a.digitCount + b.digitCount; + + unsigned char *buffer = new unsigned char[n]; + + BigInt::longMultiply( a.digits, a.digitCount, + b.digits, b.digitCount, buffer); + + unsigned char *bc(buffer); +#endif /*KARATSUBA*/ + + BigInt bigIntResult; //we assume it's a positive number + if (a.positive != b.positive) + bigIntResult.positive = false; + bigIntResult.expandTo(n + 10); + std::copy(bc, bc + n, bigIntResult.digits); + for (unsigned long int i = n - 1; i > 0L; i--) + { + if (bigIntResult.digits[i]) + { + bigIntResult.digitCount = i + 1; + break; + } + } + delete[] buffer; + + return bigIntResult; +} + +BigInt &BigInt::operator*=(const BigInt &number) +{ + *this = *this * number; + return *this; +} + +BigInt operator /(const BigInt &a, const BigInt &b) +{ + if (b.EqualsZero()) + throw "Error BIGINT11: Attempt to divide by zero."; + + //we don't want to call this function twice + int comparison(BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount)); + + //if a == 0 or |a| < |b| + if (a.EqualsZero() || comparison == 2) + return BigIntZero; + + //if a == b + if (comparison == 0) + { + if (a.positive == b.positive) + return BigIntOne; + else + return -BigIntOne; + } + + BigInt quotient, remainder; + BigInt::divide(a, b, quotient, remainder); + //adjust the sign (positive by default) + if (a.positive != b.positive) + quotient.positive = false; + return quotient; +} + +BigInt &BigInt::operator /=(const BigInt &number) +{ + *this = *this / number; + return *this; +} + +BigInt operator%(const BigInt &a, const BigInt &b) +{ + if (b.EqualsZero()) + throw "Error BIGINT12: Attempt to divide by zero."; + + //we don't want to call this function twice + int comparison(BigInt::compareNumbers( a.digits, a.digitCount, + b.digits, b.digitCount)); + + //a == b + if (comparison == 0) + return BigIntZero; + + //if a < b + if (comparison == 2 && a.positive) + return a; + + BigInt quotient, remainder; + BigInt::divide(a, b, quotient, remainder); + if (!a.positive && !remainder.EqualsZero()) + remainder.positive = false; + return remainder; +} + +BigInt &BigInt::operator%=(const BigInt &number) +{ + *this = *this % number; + return *this; +} + +/* Returns *this to the power of n + * using the fast Square and Multiply algorithm. */ +BigInt BigInt::GetPower(unsigned long int n) const +{ + BigInt result(BigIntOne); + BigInt base(*this); + + while (n) + { + //if n is odd + if (n & 1) + { + result = result * base; + n--; + } + n /= 2; + base = base * base; + } + + //number was negative and the exponent is odd, the result is negative + if (!positive && (n & 1)) + result.positive = false; + return result; +} + +/* *this = *this to the power of n. */ +void BigInt::SetPower(unsigned long int n) +{ + *this = (*this).GetPower(n); +} + +/* Returns *this to the power of n + * using the fast Square and Multiply algorithm. */ +BigInt BigInt::GetPower(BigInt n) const +{ + if (!n.positive) + throw "Error BIGINT13: Negative exponents not supported!"; + + BigInt result(BigIntOne); + BigInt base(*this); + BigInt bigIntTwo(BigIntOne + BigIntOne); + + while (!n.EqualsZero()) + { + //if n is odd + if (n.digits[0] & 1) + { + result = result * base; + n--; + } + n = n / bigIntTwo; + base = base * base; + } + + //number was negative and the exponent is odd, the result is negative + if (!positive && (n.digits[0] & 1)) + result.positive = false; + return result; +} + +/* *this = *this to the power of n. */ +void BigInt::SetPower(BigInt n) +{ + *this = (*this).GetPower(n); +} + +/* Returns (*this to the power of b) mod n. */ +BigInt BigInt::GetPowerMod(const BigInt &b, const BigInt &n) const +{ + BigInt a(*this); + a.SetPowerMod(b, n); + return a; +} + +/* *this = (*this to the power of b) mod n. */ +void BigInt::SetPowerMod(const BigInt &b, const BigInt &n) +{ + if (!b.positive) + throw "Error BIGINT14: Negative exponent not supported."; + //we will need this value later, since *this is going to change + const BigInt a(*this); + //temporary variables + BigInt bCopy(b), q, r; + const BigInt two(BigIntOne + BigIntOne); + + //first we will find the binary representation of b + std::vector bits; + while (!bCopy.EqualsZero()) + { + BigInt::divide(bCopy, two, q, r); + bCopy = q; + if (r.EqualsZero()) + bits.push_back(false); + else + bits.push_back(true); + } + + //do the exponentiating + *this = BigIntOne; + for (int i = (int) bits.size() - 1; i >= 0; i--) + { + BigInt::divide(*this * *this, n, q, *this); + if (bits[i]) + BigInt::divide(*this * a, n, q, *this); + } +} + +/* Returns the nth digit read-only, zero-based, right-to-left. */ +unsigned char BigInt::GetDigit(unsigned long int index) const +{ + if (index >= digitCount) + throw "Error BIGINT15: Index out of range."; + + return digits[index]; +} + +/* Returns the nth digit, zero-based, right-to-left. */ +void BigInt::SetDigit(unsigned long int index, unsigned char value) +{ + if (index >= digitCount) + throw "Error BIGINT15: Index out of range."; + if (value > 9) + throw "Error BIGINT16: Digit value out of range."; + + digits[index] = value; +} + +/* Returns the value of BigInt as std::string. */ +std::string BigInt::ToString(bool forceSign) const +{ + (void)forceSign; // avoid unused warning + std::string number; + if (!positive) + number.push_back('-'); + for (int i = digitCount - 1; i >= 0; i--) + number.push_back(char(digits[i]) + '0'); + + return number; +} + +/* Returns the absolute value. */ +BigInt BigInt::Abs() const +{ + return ((positive) ? *this : -(*this)); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h b/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h new file mode 100644 index 0000000..c78dc11 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h @@ -0,0 +1,294 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * BigInt.h + * + * Author: Nedim Srndic + * Release date: 14th of March 2008 + * + * A class representing a positive or negative integer that may + * be too large to fit in any of the standard C++ integer types + * (i. e. 2^128 is "just" 39 digits long). + * The digits are stored in a dinamic array of tipe unsigned char*, + * with values from 0 to 9 (not '0' to '9'), so that the CPU can + * add/subtract individual digits. + * + * The array has "length" memory locations, one byte each (the size of + * unsigned char is probably one byte). There are "digitCount" digits actually + * in use, the rest is spare space. + * The number of digits is constrained by available memory and the limit of the + * unsigned long int type used for indexing (the "length" property). + * The individual digits are stored right-to-left, to speed up computing and + * allow for faster growth of numbers (no need to reallocate memory when + * the digitCount grows). + * + * The class handles its own memory management. There are no memory leaks + * reported until this date. + * When creating a BigInt from const char* or unsigned long int, + * copying from an other BigInt with (digitCount + 2 <= length) + * (soon to be full), new memory is allocated and + * length is adjusted to (length * FACTOR + 1). This is done to expand the + * capacity of the digits array to accomodate potential new digits. + * When assigning a BigInt "bInt" that is twice as small or bigger than *this, + * the length is set to (bInt.length + 2). + * + * BigInt supports: + * + * - addition (unary +, binary +, +=, prefix ++, postfix ++) + * + * - subtraction (unary -, binary -, -=, prefix --, postfix --) + * + * - multiplication (*, *=) + * For multiplication, one can choose between the Square and multiply + * or Karatsuba algorithm, or long multiplication at compile time + * (this can be done by defining or undefining the macro "KARATSUBA" + * in BigInt.cpp). + * The Karatsuba algorithm multiplies integers in O(n^log2(3)) + * complexity. log2(3) is approximately 1.585, so this should be + * significantly faster than long multiplication, if the numbers are + * big enough. Currently, the long multiplication is better implemented, + * and runs faster than the Karatsuba multiplication for numbers shorter + * than about 100 digits. + * + * - C-style integer division (/, /=) + * + * - C-style integer division remainder (%, %=) + * When calculating the remainder, the number is first divided. + * + * - comparison (==, !=, <, <=, >, >=) + * All of the <, <=, >, >= operators are equally fast. + * + * - exponentiation (GetPower(), SetPower(), GetPowerMod(), SetPowerMod()) + * For exponentiation, the Exponantiation by squaring + * (or Square and multiply or Binary exponentiation) algorithm is used. + * It uses O(log(n)) multiplications and therefore is significantly faster + * than multiplying x with itself n-1 times. + * + * In addition to mathematical operations, BigInt supports: + * + * - automatic conversion from const char *, std::string and unsigned long int + * - safe construction, copying, assignment and destruction + * - automatic conversion to std::string + * - writing to the standard output (operator <<(std::ostream, BigInt)) + * - reading from the standard input (operator >>(std::istream, BigInt)) + * - getting and setting individual digits (GetDigit(), SetDigit()) + * - returning the number of digits (Length()) + * - returning a string of digits (ToString()) + * This can be useful for human-readable output. + * - returning a value indicating wether the number is odd (IsOdd()) + * - returning a value indicating wether the number is positive (IsPositive()) + * - returning a value indicating wether the BigInt equals zero (EqualsZero()) + * The fastest way to determine this. + * - returning absolute value (Abs()) + * + * There are a few static constants defined in this file: + * + * - BigIntZero : a zero of type BigInt + * If you need a zero fast, use this. + * - BigIntOne : a one of type BigInt + * If you need a one fast, use this. + * + * **************************************************************************** +*/ + +#ifndef BIGINT_H_ +#define BIGINT_H_ + +#include //ostream, istream +#include //sqrt() +#include //ToString(), BigInt(std::string) + +class BigInt +{ + private: + /* An array of digits stored right to left, + * i.e. int 345 = unsigned char {[5], [4], [3]} */ + unsigned char *digits; + // The total length of the allocated memory + unsigned long int length; + // Number of digits + unsigned long int digitCount; + // Sign + bool positive; + /* Multiplication factor for the length property + * when creating or copying objects. */ + static const double FACTOR; + /* Transforms the number from unsigned long int to unsigned char[] + * and pads the result with zeroes. Returns the number of digits. */ + static unsigned long int int2uchar( unsigned long int number, + unsigned char *digits, + unsigned long int padding); + /* Converts ASCII digits to equivalent unsigned char numeric values. */ + static void char2uchar( unsigned char *array, + unsigned long int length); + /* Check if all ASCII values are digits '0' to '9'. */ + static bool allCharsAreDigits( const char *array, + unsigned long int length); + /* Compares two BigInt. If the last two arguments are + * omitted, the comparison is sign-insensitive (comparison by + * absolute value). Returns 0 if a == b, 1 if a > b, 2 if a < b. */ + static int compareNumbers( unsigned char *a, unsigned long int na, + unsigned char *b, unsigned long int nb, + bool aPositive = true, + bool bPositive = true); + /* Multiplies two unsigned char[] using the Divide and Conquer + * a.k.a. Karatsuba algorithm .*/ + static void karatsubaMultiply( unsigned char *a, unsigned char *b, + unsigned long int n, + unsigned char *buffer); + /* Multiplies two unsigned char[] the long way. */ + static void longMultiply( unsigned char *a, unsigned long int na, + unsigned char *b, unsigned long int nb, + unsigned char *result); + /* Simple addition, used by the multiply function. + * Returns the remaining carry. */ + static unsigned char quickAdd( unsigned char *a, unsigned char *b, + unsigned long int n); + /* Simple subtraction, used by the multiply function. */ + static void quickSub( unsigned char *a, unsigned char *b, + unsigned char *end, unsigned long int n); + /* Divides two BigInt numbers. */ + static void divide( const BigInt ÷nd, const BigInt &divisor, + BigInt "ient, BigInt &remainder); + /* Returns the value of the specified unsigned char[] as long int. */ + static unsigned long int toInt(unsigned char *digits, int n); + /* Saves the sum of two unsigned char* shorter and longer into result. + * It must be nShorter <= nLonger. If doFill == true, it fills the + * remaining free places with zeroes (used in KaratsubaMultiply()). + * Returns true if there was an overflow at the end (meaning that + * the result.digitCount was longer.digitCount + 1. */ + static bool add(unsigned char *shorter, unsigned long int nShorter, + unsigned char *longer, unsigned long int nLonger, + unsigned char *result, int nResult, + bool doFill = true); + /* Shifts the digits n places left. */ + BigInt &shiftLeft(unsigned long int n); + /* Shifts the digits n places right. */ + BigInt &shiftRight(unsigned long int n); + /* Expands the digits* to n. */ + void expandTo(unsigned long int n); + public: + BigInt(); + BigInt(const char *charNum); + BigInt(unsigned long int intNum); + BigInt(const std::string &str); + BigInt(const BigInt &number); + BigInt &operator =(const BigInt &rightNumber); + ~BigInt(); + operator std::string() const; + friend std::ostream &operator <<( std::ostream &cout, + const BigInt &number); + friend std::istream &operator >>( std::istream &cin, + BigInt &number); + friend bool operator <(const BigInt &a, const BigInt &b); + friend bool operator <=(const BigInt &a, const BigInt &b); + friend bool operator >(const BigInt &a, const BigInt &b); + friend bool operator >=(const BigInt &a, const BigInt &b); + friend bool operator ==(const BigInt &a, const BigInt &b); + friend bool operator !=(const BigInt &a, const BigInt &b); + friend BigInt operator + (const BigInt &a, const BigInt &b); + BigInt &operator+(); + BigInt &operator++(); + BigInt operator++(int); + BigInt &operator+=(const BigInt &number); + BigInt operator-() const; + friend BigInt operator-(const BigInt &a, const BigInt &b); + BigInt &operator--(); + BigInt operator--(int); + BigInt &operator-=(const BigInt &number); + friend BigInt operator*(const BigInt &a, const BigInt &b); + BigInt &operator*=(const BigInt &number); + friend BigInt operator/(const BigInt &a, const BigInt &b); + BigInt &operator/=(const BigInt &number); + friend BigInt operator%(const BigInt &a, const BigInt &b); + BigInt &operator%=(const BigInt &number); + /* Returns *this to the power of n + * using the fast Square and Multiply algorithm. */ + BigInt GetPower(unsigned long int n) const; + /* *this = *this to the power of n. */ + void SetPower(unsigned long int n); + /* Returns *this to the power of n + * using the fast Square and Multiply algorithm. */ + BigInt GetPower(BigInt n) const; + /* *this = *this to the power of n. */ + void SetPower(BigInt n); + /* Returns (*this to the power of b) mod n. */ + BigInt GetPowerMod(const BigInt &b, const BigInt &n) const; + /* *this = (*this to the power of b) mod n. */ + void SetPowerMod(const BigInt &b, const BigInt &n); + /* Returns the 'index'th digit (zero-based, right-to-left). */ + unsigned char GetDigit(unsigned long int index) const; + /* Sets the value of 'index'th digit + * (zero-based, right-to-left) to 'value'. */ + void SetDigit(unsigned long int index, unsigned char value); + /* Returns the number of digits. */ + unsigned long int Length() const; + /* Returns true if *this is positive, otherwise false. */ + bool IsPositive() const; + /* Returns true if *this is odd, otherwise false. */ + bool IsOdd() const; + /* Returns the value of BigInt as std::string. */ + std::string ToString(bool forceSign = false) const; + /* Returns a value indicating whether *this equals 0. */ + bool EqualsZero() const; + /* Returns the absolute value. */ + BigInt Abs() const; +}; + +inline BigInt::~BigInt() +{ + delete[] digits; +} + +inline BigInt &BigInt::operator+() +{ + return *this; +} + +/* Returns the number of digits. */ +inline unsigned long int BigInt::Length() const +{ + return digitCount; +} + +/* Returns true if *this is positive, otherwise false. */ +inline bool BigInt::IsPositive() const +{ + return positive; +} + +/* Returns true if *this is odd, otherwise false. */ +inline bool BigInt::IsOdd() const +{ + return digits[0] & 1; +} + +/* Returns a value indicating whether *this equals 0. */ +inline bool BigInt::EqualsZero() const +{ + return digitCount == 1 && digits[0] == 0; +} + +// A BigInt number with the value of 0. +static const BigInt BigIntZero; +// A BigInt number with the value of 1. +static const BigInt BigIntOne(1L); + + +#endif /*BIGINT_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp new file mode 100644 index 0000000..adc4a1f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp @@ -0,0 +1,37 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * Key.cpp + * + * Author: Nedim Srndic + * Release date: 5th of September 2008 + * + * This file contains the implementation for the Key class. + * + * **************************************************************************** + */ + +#include "Key.h" + +std::ostream &operator<<(std::ostream &, const Key &key) +{ + return std::cout + << "Modulus: " << key.GetModulus() << std::endl + << "Exponent: " << key.GetExponent(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/Key.h b/SQLiteStudio3/coreSQLiteStudio/rsa/Key.h new file mode 100644 index 0000000..b193e2c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/Key.h @@ -0,0 +1,59 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * Key.h + * + * Author: Nedim Srndic + * Release date: 16th of June 2008 + * + * A class representing a public or private RSA key. + * + * A public or private RSA key consists of a modulus and an exponent. In this + * implementation an object of type BigInt is used to store those values. + * + * **************************************************************************** + */ + +#ifndef KEY_H_ +#define KEY_H_ + +#include "BigInt.h" +#include + +class Key +{ + private: + BigInt modulus; + BigInt exponent; + public: + Key(const BigInt &modulus, const BigInt &exponent) : + modulus(modulus), exponent(exponent) + {} + const BigInt &GetModulus() const + { + return modulus; + } + const BigInt &GetExponent() const + { + return exponent; + } + friend std::ostream &operator<<(std::ostream &, const Key &key); +}; + +#endif /*KEY_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp new file mode 100644 index 0000000..7780288 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp @@ -0,0 +1,37 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * KeyPair.cpp + * + * Author: Nedim Srndic + * Release date: 22th of July 2008 + * + * This file contains the implementation for the KeyPair class. + * + * **************************************************************************** + */ + +#include "KeyPair.h" + +std::ostream &operator <<(std::ostream &, const KeyPair &k) +{ + return std::cout + << "Private key:" << std::endl << k.GetPrivateKey() << std::endl + << "Public key:" << std::endl << k.GetPublicKey(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h b/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h new file mode 100644 index 0000000..929ffe9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h @@ -0,0 +1,58 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * KeyPair.h + * + * Author: Nedim Srndic + * Release date: 17th of June 2008 + * + * A class representing a public/private RSA keypair. + * + * A keypair consists of a public key and a matching private key. + * + * **************************************************************************** + */ + +#ifndef KEYPAIR_H_ +#define KEYPAIR_H_ + +#include "Key.h" +#include + +class KeyPair +{ + private: + const Key privateKey; + const Key publicKey; + public: + KeyPair(Key privateKey, Key publicKey): + privateKey(privateKey), publicKey(publicKey) + {} + const Key &GetPrivateKey() const + { + return privateKey; + } + const Key &GetPublicKey() const + { + return publicKey; + } + friend std::ostream &operator <<(std::ostream &, const KeyPair &k); +}; + +#endif /*KEYPAIR_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.cpp new file mode 100644 index 0000000..7f38f55 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.cpp @@ -0,0 +1,198 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * PrimeGenerator.cpp + * + * Author: Nedim Srndic + * Release date: 14th of March 2008 + * + * This file contains the implementation for the PrimeGenerator class. + * + * There is a static constant defined in this file: + * + * - RandMax : RAND_MAX (defined in cstdlib) of type BigInt + * Mainly used for speedup in the Generate member function. + * Represents the largest random unsigned long integer that a particular + * platform can generate. This is platform-specific. + * + * **************************************************************************** + */ + +#include "PrimeGenerator.h" +#include +#include // rand() + +/* Generates a random number with digitCount digits. + * Returns it by reference in the "number" parameter. */ +void PrimeGenerator::MakeRandom(BigInt &number, unsigned long int digitCount) +{ + //the new number will be created using a string object (newNum), + //and later converted into a BigInt + std::string newNum; + newNum.resize(digitCount); + unsigned long int tempDigitCount(0); + + //generate random digits + while (tempDigitCount < digitCount) + { + unsigned long int newRand(std::rand()); + + //10 is chosen to skip the first digit, because it might be + //statistically <= n, where n is the first digit of RAND_MAX + while (newRand >= 10) + { + newNum[tempDigitCount++] = (newRand % 10) + '0'; + newRand /= 10; + if (tempDigitCount == digitCount) + break; + } + } + + //make sure the leading digit is not zero + if (newNum[0] == '0') + newNum[0] = (std::rand() % 9) + 1 + '0'; + number = newNum; +} + +/* Generates a random number such as 1 <= number < 'top'. + * Returns it by reference in the 'number' parameter. */ +void PrimeGenerator::makeRandom(BigInt &number, const BigInt &top) +{ + //randomly select the number of digits for the random number + unsigned long int newDigitCount = (rand() % top.Length()) + 1; + MakeRandom(number, newDigitCount); + //make sure number < top + while (number >= top) + MakeRandom(number, newDigitCount); +} + +/* Creates an odd BigInt with the specified number of digits. + * Returns it by reference in the "number" parameter. */ +void PrimeGenerator::makePrimeCandidate(BigInt &number, + unsigned long int digitCount) +{ + PrimeGenerator::MakeRandom(number, digitCount); + //make the number odd + if (!number.IsOdd()) + number.SetDigit(0, number.GetDigit(0) + 1); + //make sure the leading digit is not a zero + if (number.GetDigit(number.Length() - 1) == 0) + number.SetDigit(number.Length() - 1, (std::rand() % 9) + 1); +} + +/* Tests the primality of the given _odd_ number using the + * Miller-Rabin probabilistic primality test. Returns true if + * the tested argument "number" is a probable prime with a + * probability of at least 1 - 4^(-k), otherwise false. */ +bool PrimeGenerator::isProbablePrime( const BigInt &number, + unsigned long int k) +{ + //first we need to calculate such a and b, that + //number - 1 = 2^a * b, a and b are integers, b is odd + BigInt numberMinusOne(number - BigIntOne); + unsigned long int a(0); + BigInt temp(numberMinusOne); + BigInt b, quotient; + static const BigInt two(BigIntOne + BigIntOne); + + while (b.EqualsZero()) + { + //temp = quotient * 2 + remainder + + //PrimeGenerator used to be a friend of BigInt, so the following + //statement produced the result in one call to BigInt::divide() +// BigInt::divide(temp, two, quotient, b); + //That doesn't work any more, so we have to use two calls + quotient = temp / two; + b = temp % two; + temp = quotient; + a++; + } + b = temp * two + b; + a--; + + //test with k different possible witnesses to ensure that the probability + //that "number" is prime is at least 1 - 4^(-k) + for (unsigned long int i = 0; i < k; i++) + { + PrimeGenerator::makeRandom(temp, number); + + if (isWitness(temp, number, b, a, numberMinusOne)) + return false; //definitely a composite number + } + return true; //a probable prime +} + +/* Returns true if "candidate" is a witness for the compositeness + * of "number", false if "candidate" is a strong liar. "exponent" + * and "squareCount" are used for computation */ +bool PrimeGenerator::isWitness( BigInt candidate, + const BigInt &number, + const BigInt &exponent, + unsigned long int squareCount, + const BigInt &numberMinusOne) +{ + //calculate candidate = (candidate to the power of exponent) mod number + candidate.SetPowerMod(exponent, number); + //temporary variable, used to call the divide function + BigInt quotient; + + for (unsigned long int i = 0; i < squareCount; i++) + { + bool maybeWitness(false); + if (candidate != BigIntOne && candidate != numberMinusOne) + maybeWitness = true; + + //PrimeGenerator used to be a friend of BigInt, so the following + //statement produced the result in one call to BigInt::divide() +// BigInt::divide(candidate * candidate, number, quotient, candidate); + //That doesn't work any more, so we have to use two calls + candidate = candidate * candidate; + quotient = (candidate) / number; + candidate = (candidate) % number; + if (maybeWitness && candidate == BigIntOne) + return true; //definitely a composite number + } + + if (candidate != BigIntOne) + return true; //definitely a composite number + + return false; //probable prime +} + +/* Returns a probable prime number "digitCount" digits long, + * with a probability of at least 1 - 4^(-k) that it is prime. */ +BigInt PrimeGenerator::Generate(unsigned long int digitCount, + unsigned long int k) +{ + if (digitCount < 3) + throw "Error PRIMEGENERATOR00: Primes less than 3 digits long " + "not supported."; + + BigInt primeCandidate; + PrimeGenerator::makePrimeCandidate(primeCandidate, digitCount); + while (!isProbablePrime(primeCandidate, k)) + { + //select the next odd number and try again + primeCandidate = primeCandidate + 2; + if (primeCandidate.Length() != digitCount) + PrimeGenerator::makePrimeCandidate(primeCandidate, digitCount); + } + return primeCandidate; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.h b/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.h new file mode 100644 index 0000000..8a9dfac --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.h @@ -0,0 +1,71 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * PrimeGenerator.h + * + * A class used to generate large prime or random numbers. + * + * Author: Nedim Srndic + * Release date: 14th of March 2008 + * + * **************************************************************************** + */ + +#ifndef PRIMEGENERATOR_H_ +#define PRIMEGENERATOR_H_ + +#include "BigInt.h" + +class PrimeGenerator +{ + private: + /* Generates a random "number" such as 1 <= "number" < "top". + * Returns it by reference in the "number" parameter. */ + static void makeRandom( BigInt &number, + const BigInt &top); + /* Creates an odd BigInt with the specified number of digits. + * Returns it by reference in the "number" parameter. */ + static void makePrimeCandidate( BigInt &number, + unsigned long int digitCount); + /* Tests the primality of the given _odd_ number using the + * Miller-Rabin probabilistic primality test. Returns true if + * the tested argument "number" is a probable prime with a + * probability of at least 1 - 4^(-k), otherwise false. */ + static bool isProbablePrime(const BigInt &number, + unsigned long int k); + /* Returns true if "candidate" is a witness for the compositeness + * of "number", false if "candidate" is a strong liar. "exponent" + * and "squareCount" are used for computation */ + static bool isWitness( BigInt candidate, + const BigInt &number, + const BigInt &exponent, + unsigned long int squareCount, + const BigInt &numberMinusOne); + public: + /* Generates a random number with digitCount digits. + * Returns it by reference in the "number" parameter. */ + static void MakeRandom( BigInt &number, + unsigned long int digitCount); + /* Returns a probable prime number "digitCount" digits long, + * with a probability of at least 1 - 4^(-k) that it is prime. */ + static BigInt Generate( unsigned long int digitCount, + unsigned long int k = 3); +}; + +#endif /*PRIMEGENERATOR_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp new file mode 100644 index 0000000..0d8e921 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp @@ -0,0 +1,394 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * RSA.cpp + * + * Author: Nedim Srndic + * Release date: 16th of June 2008 + * + * This file contains the implementation for the RSA class. + * + * **************************************************************************** + */ + +#include "RSA.h" +#include "Key.h" //Key +#include "KeyPair.h" //KeyPair +#include "PrimeGenerator.h" //Generate() +#include //string +#include //ifstream, ofstream + +using std::string; + +/* Returns the greatest common divisor of the two arguments + * "a" and "b", using the Euclidean algorithm. */ +BigInt RSA::GCD(const BigInt &a, const BigInt &b) +{ + if (b.EqualsZero()) + return a; + else + return RSA::GCD(b, a % b); +} + +/* Solves the equation + * d = ax + by + * given a and b, and returns d, x and y by reference. + * It uses the Extended Euclidean Algorithm */ +void RSA::extendedEuclideanAlgorithm( const BigInt &a, const BigInt &b, + BigInt &d, BigInt &x, BigInt &y) +{ + if (b.EqualsZero()) + { + d = a; + x = BigIntOne; + y = BigIntZero; + return; + } + RSA::extendedEuclideanAlgorithm(b, a % b, d, x, y); + BigInt temp(x); + x = y; + y = temp - a / b * y; +} + +/* Solves the equation + * ax is congruent to b (mod n), + * given a, b and n finds x. */ +BigInt RSA::solveModularLinearEquation( const BigInt &a, + const BigInt &b, + const BigInt &n) +{ + BigInt p, q, r; + RSA::extendedEuclideanAlgorithm(a, n, p, q, r); + if ((b % p).EqualsZero()) // This has to evaluate to 'true'. + return (q * (b / p)) % n; + else + throw "Error RSA00: Error in key generation."; // Detect mistakes. +} + +/* Throws an exception if "key" is too short to be used. */ +void RSA::checkKeyLength(const Key &key) +{ + // Minimum required key length is around 24 bits. (In-house requirement) + if (key.GetModulus().Length() < 8) + throw "Error RSA01: Keys must be at least 8 digits long."; +} + +/* Transforms a std::string message into a BigInt message. + * Every ASCII character of the original message is replaced by it's + * ASCII value and appended to the end of the newly created BigInt object + * 'decoded' as a three-digit number, from left to right. */ +BigInt RSA::encode(const string &message) +{ + // The new number will be created using a string object (encoded), + // and converted into a BigInt on return. + string encoded; + encoded.resize(message.length() * 3 + 1); + unsigned long int index = message.length() * 3; + for (unsigned long int i(0); i < message.length(); i++) + { + // Encode the characters using their ASCII values' digits as + // BigInt digits. + unsigned char ASCII = message[i]; + encoded[index - 2] = (ASCII % 10) + '0'; + ASCII /= 10; + encoded[index - 1] = (ASCII % 10) + '0'; + encoded[index] = (ASCII / 10) + '0'; + index -= 3; + } + // We add an special symbol '1' to the beginning of the string 'encoded' + // to make sure that the returned BigInt doesn't begin with a zero. We also + // need to make sure we remove that '1' when decoding (see RSA::decode()). + encoded[0] = '1'; + return encoded; +} + +/* Transforms a BigInt cyphertext into a std::string cyphertext. */ +string RSA::decode(const BigInt &message) +{ + string decoded; + // The special symbol '1' we added to the beginning of the encoded message + // will now be positioned at message[message.Length() - 1], and + // message.Length() -1 must be divisible by 3 without remainder. Thus we + // can ignore the special symbol by only using digits in the range + // from message[0] to message[message.Length() - 2]. + for (unsigned long int i(0); i < message.Length() / 3; i++) + { + // Decode the characters using the ASCII values in the BigInt digits. + char ASCII = 100 * char(message.GetDigit(i * 3)); + ASCII += 10 * char(message.GetDigit(i * 3 + 1)); + decoded.push_back(ASCII + char(message.GetDigit(i * 3 + 2))); + } + return decoded; +} + +/* Encrypts a "chunk" (a small part of a message) using "key" */ +string RSA::encryptChunk(const string &chunk, const Key &key) +{ + // First encode the chunk, to make sure it is represented as an integer. + BigInt a = RSA::encode(chunk); + // The RSA encryption algorithm is a congruence equation. + a.SetPowerMod(key.GetExponent(), key.GetModulus()); + return a.ToString(); +} + +/* Decrypts a "chunk" (a small part of a message) using "key" */ +string RSA::decryptChunk(const BigInt &chunk, const Key &key) +{ + BigInt a = chunk; + // The RSA decryption algorithm is a congruence equation. + a.SetPowerMod(key.GetExponent(), key.GetModulus()); + // Decode the message to a readable form. + return RSA::decode(a); +} + +/* Encrypts a string "message" using "key". */ +std::string RSA::encryptString(const std::string &message, const Key &key) +{ + //partition the message into biggest possible encryptable chunks + const unsigned long int chunkSize(((key.GetModulus().Length() - 2) / 3)); + const unsigned long int chunkCount = message.length() / chunkSize; + + string cypherText; + for (unsigned long int i(0); i < chunkCount; i++) + { + // Get the next chunk. + string chunk(message.substr(i * chunkSize, chunkSize)); + chunk = RSA::encryptChunk(chunk, key); + // Put a ' ' between the chunks so that we can separate them later. + cypherText.append(chunk.append(" ")); + } + // If the last chunk has the same size as the others, we are finished. + if (chunkSize * chunkCount == message.length()) + return cypherText; + + // Handle the last chunk. It is smaller than the others. + const unsigned long int lastChunkSize = message.length() % chunkSize; + string lastChunk(message.substr(chunkCount * chunkSize, lastChunkSize)); + lastChunk = RSA::encryptChunk(lastChunk, key); + return cypherText.append(lastChunk.append(" ")); +} + +/* Decrypts a string "message" using "key". */ +std::string RSA::decryptString(const std::string &cypherText, const Key &key) +{ + // Partition the cypherText into chunks. They are seperated by ' '. + string message; + long int i(0), j(0); + while ((j = cypherText.find(' ', i)) != -1) + { + // Get the chunk. + BigInt chunk(cypherText.substr(i, j - i)); + if (chunk >= key.GetModulus()) + throw "Error RSA02: Chunk too large."; + + // Decrypt the chunk and store the message. + string text = RSA::decryptChunk(chunk, key); + message.append(text); + i = j + 1; + } + return message; +} + +/* Tests the file for 'eof', 'bad ' errors and throws an exception. */ +void RSA::fileError(bool eof, bool bad) +{ + if (eof) + throw "Error RSA03: Unexpected end of file."; + else if (bad) + throw "Error RSA04: Bad file?"; + else + throw "Error RSA05: File contains unexpected data."; +} + +/* Returns the string "message" RSA-encrypted using the key "key". */ +string RSA::Encrypt(const string &message, const Key &key) +{ + RSA::checkKeyLength(key); + + return RSA::encryptString(message, key); +} + +/* Encrypts the file "sourceFile" using the key "key" and saves + * the result into the file "destFile". */ +void RSA::Encrypt( const char *sourceFile, const char *destFile, + const Key &key) +{ + RSA::checkKeyLength(key); + + //open the input and output files + std::ifstream source(sourceFile, std::ios::in | std::ios::binary); + if (!source) + throw "Error RSA06: Opening file \"sourceFile\" failed."; + std::ofstream dest(destFile, std::ios::out | std::ios::binary); + if (!dest) + throw "Error RSA07: Creating file \"destFile\" failed."; + + //find the source file length + source.seekg(0, std::ios::end); + const unsigned long int fileSize = source.tellg(); + source.seekg(0, std::ios::beg); + + //create an input buffer + const unsigned long int bufferSize = 4096; + char buffer[bufferSize]; + + //encrypt file chunks + const unsigned long int chunkCount = fileSize / bufferSize; + for (unsigned long int i(0); i <= chunkCount; i++) + { + unsigned long int readLength; + //read the chunk + if (i == chunkCount) //if it's the last one + readLength = fileSize % bufferSize; + else + readLength = sizeof buffer; + source.read(buffer, readLength); + if (!source) + RSA::fileError(source.eof(), source.bad()); + + //encrypt the chunk + std::string chunk(buffer, readLength); + chunk = RSA::encryptString(chunk, key); + //write the chunk + dest.write(chunk.c_str(), chunk.length()); + if (!dest) + RSA::fileError(dest.eof(), dest.bad()); + } + + source.close(); + dest.close(); +} + +/* Returns the string "cypherText" RSA-decrypted using the key "key". */ +string RSA::Decrypt(const string &cypherText, const Key &key) +{ + RSA::checkKeyLength(key); + + return RSA::decryptString(cypherText, key); +} + +/* Decrypts the file "sourceFile" using the key "key" and saves + * the result into the file "destFile". */ +void RSA::Decrypt( const char *sourceFile, const char *destFile, + const Key &key) +{ + RSA::checkKeyLength(key); + + //open the input and output files + std::ifstream source(sourceFile, std::ios::in | std::ios::binary); + if (!source) + throw "Error RSA08: Opening file \"sourceFile\" failed."; + std::ofstream dest(destFile, std::ios::out | std::ios::binary); + if (!dest) + throw "Error RSA09: Creating file \"destFile\" failed."; + + //find the source file length + source.seekg(0, std::ios::end); + const unsigned long int fileSize = source.tellg(); + source.seekg(0, std::ios::beg); + + //create an input buffer + const unsigned long int bufferSize = 8192; + char buffer[bufferSize]; + unsigned long int readCount = 0; + + while (readCount < fileSize) + { + unsigned long int readLength; + //read new data + if (fileSize - readCount >= bufferSize) //if it's not the last one + readLength = sizeof buffer; + else + readLength = fileSize - readCount; + source.read(buffer, readLength); + if (!source) + RSA::fileError(source.eof(), source.bad()); + + //find the next chunk + std::string chunk(buffer, readLength); + chunk = chunk.substr(0, chunk.find_last_of(' ', chunk.length()) + 1); + readCount += chunk.length(); + source.seekg(readCount, std::ios::beg); + //decrypt the chunk + chunk = RSA::decryptString(chunk, key); + //write the chunk + dest.write(chunk.c_str(), chunk.length()); + if (!dest) + RSA::fileError(dest.eof(), dest.bad()); + } + + source.close(); + dest.close(); +} + +/* Generates a public/private keypair. The keys are retured in a + * KeyPair. The generated keys are 'digitCount' or + * 'digitCount' + 1 digits long. */ +KeyPair RSA::GenerateKeyPair( unsigned long int digitCount, + unsigned long int k) +{ + if (digitCount < 8) + throw "Error RSA10: Keys must be at least 8 digits long."; + + //generate two random numbers p and q + BigInt p(PrimeGenerator::Generate(digitCount / 2 + 2, k)); + BigInt q(PrimeGenerator::Generate(digitCount / 2 - 1, k)); + + //make sure they are different + while (p == q) + { + p = PrimeGenerator::Generate(digitCount / 2 + 1, k); + } + + //calculate the modulus of both the public and private keys, n + BigInt n(p * q); + + //calculate the totient phi + BigInt phi((p - BigIntOne) * (q - BigIntOne)); + + //select a small odd integer e that is coprime with phi and e < phi + //usually 65537 is used, and we will use it too if it fits + //it is recommended that this be the least possible value for e + BigInt e("65537"); + + //make sure the requirements are met + while (RSA::GCD(phi, e) != BigIntOne || e < "65537" || !e.IsOdd()) + { + PrimeGenerator::MakeRandom(e, 5); + } + + //now we have enough information to create the public key + //e is the public key exponent, n is the modulus + Key publicKey(n, e); + + //calculate d, d * e = 1 (mod phi) + BigInt d(RSA::solveModularLinearEquation(e, BigIntOne, phi)); + + //we need a positive private exponent + if (!d.IsPositive()) + return RSA::GenerateKeyPair(digitCount, k); + + //we can create the private key + //d is the private key exponent, n is the modulus + Key privateKey(n, d); + + //finally, the keypair is created and returned + KeyPair newKeyPair(privateKey, publicKey); + return newKeyPair; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h b/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h new file mode 100644 index 0000000..501261e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h @@ -0,0 +1,130 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see . + * + * RSA.h + * + * Author: Nedim Srndic + * Release date: 16th of June 2008 + * + * An implementation of the RSA public-key cryptography algorithm. + * + * RSA supports: + * + * - Message encryption (string and file) (Encrypt()) + * - Message decryption (string and file) (Decrypt()) + * - Public/private keypair generation (GenerateKeyPair()) + * + * NOTE: All methods are static. Instantiation, copying and assignment of + * objects of type RSA is forbidden. + * + * NOTE: it is highly recommended to call + * std::srand(time(NULL)); + * once when the program starts and before any use of methods provided by the + * RSA class. Calling the srand() function randomizes the standard C++ + * pseudorandom number generator, so that it provides different series of + * pseudorandom numbers every time the program is run. This greatly improves + * security. + * + * **************************************************************************** + */ + +#ifndef RSA_H_ +#define RSA_H_ + +#include +#include +#include "KeyPair.h" +#include "Key.h" +#include "BigInt.h" +#include "coreSQLiteStudio_global.h" + +class API_EXPORT RSA +{ + private: + /* Instantiation of objects of type RSA is forbidden. */ + RSA() + {} + /* Copying of objects of type RSA is forbidden. */ + RSA(const RSA &rsa); + /* Assignment of objects of type RSA is forbidden. */ + RSA &operator=(const RSA &rsa); + /* Returns the greatest common divisor of the two arguments + * "a" and "b", using the Euclidean algorithm. */ + static BigInt GCD(const BigInt &a, const BigInt &b); + /* Solves the equation + * d = ax + by + * given a and b, and returns d, x and y by reference. + * It uses the Extended Euclidean Algorithm */ + static void extendedEuclideanAlgorithm( const BigInt &a, + const BigInt &b, + BigInt &d, + BigInt &x, + BigInt &y); + /* Solves the equation + * ax is congruent to b (mod n), + * given a, b and n finds x. */ + static BigInt solveModularLinearEquation( const BigInt &a, + const BigInt &b, + const BigInt &n); + /* Throws an exception if "key" is too short to be used. */ + static void checkKeyLength(const Key &key); + /* Transforms a std::string message into a BigInt message. */ + static BigInt encode(const std::string &message); + /* Transforms a BigInt cyphertext into a std::string cyphertext. */ + static std::string decode(const BigInt &message); + /* Encrypts a "chunk" (a small part of a message) using "key" */ + static std::string encryptChunk(const std::string &chunk, + const Key &key); + /* Decrypts a "chunk" (a small part of a message) using "key" */ + static std::string decryptChunk(const BigInt &chunk, + const Key &key); + /* Encrypts a string "message" using "key". */ + static std::string encryptString( const std::string &message, + const Key &key); + /* Decrypts a string "message" using "key". */ + static std::string decryptString( const std::string &cypherText, + const Key &key); + /* Tests the file for 'eof', 'bad ' errors and throws an exception. */ + static void fileError(bool eof, bool bad); + public: + /* Returns the string "message" RSA-encrypted using the key "key". */ + static std::string Encrypt( const std::string &message, + const Key &key); + /* Encrypts the file "sourceFile" using the key "key" and saves + * the result into the file "destFile". */ + static void Encrypt(const char *sourceFile, + const char *destFile, + const Key &key); + /* Decrypts the file "sourceFile" using the key "key" and saves + * the result into the file "destFile". */ + static void Decrypt(const char *sourceFile, + const char *destFile, + const Key &key); + /* Returns the string "cypherText" RSA-decrypted + * using the key "key". */ + static std::string Decrypt( const std::string &cypherText, + const Key &key); + /* Generates a public/private keypair. The keys are retured in a + * KeyPair. The generated keys are 'digitCount' or + * 'digitCount' + 1 digits long. */ + static KeyPair GenerateKeyPair( unsigned long int digitCount, + unsigned long int k = 3); +}; + +#endif /*RSA_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp new file mode 100644 index 0000000..a1ceca5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp @@ -0,0 +1,910 @@ +#include "schemaresolver.h" +#include "db/db.h" +#include "parser/parsererror.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitetablerelatedddl.h" +#include + +const char* sqliteMasterDdl = + "CREATE TABLE sqlite_master (type text, name text, tbl_name text, rootpage integer, sql text)"; +const char* sqliteTempMasterDdl = + "CREATE TABLE sqlite_temp_master (type text, name text, tbl_name text, rootpage integer, sql text)"; + +SchemaResolver::SchemaResolver(Db *db) + : db(db) +{ + parser = new Parser(db->getDialect()); +} + +SchemaResolver::~SchemaResolver() +{ + delete parser; +} + +QStringList SchemaResolver::getTables(const QString &database) +{ + QStringList tables = getObjects(database, "table"); + if (!ignoreSystemObjects) + tables << "sqlite_master" << "sqlite_temp_master"; + + return tables; +} + +QStringList SchemaResolver::getIndexes(const QString &database) +{ + QStringList indexes = getObjects(database, "index"); + if (ignoreSystemObjects) + filterSystemIndexes(indexes); + + return indexes; +} + +QStringList SchemaResolver::getTriggers(const QString &database) +{ + return getObjects(database, "trigger"); +} + +QStringList SchemaResolver::getViews(const QString &database) +{ + return getObjects(database, "view"); +} + +StrHash SchemaResolver::getGroupedIndexes(const QString &database) +{ + QStringList allIndexes = getIndexes(database); + return getGroupedObjects(database, allIndexes, SqliteQueryType::CreateIndex); +} + +StrHash SchemaResolver::getGroupedTriggers(const QString &database) +{ + QStringList allTriggers = getTriggers(database); + return getGroupedObjects(database, allTriggers, SqliteQueryType::CreateTrigger); +} + +StrHash< QStringList> SchemaResolver::getGroupedObjects(const QString &database, const QStringList &inputList, SqliteQueryType type) +{ + QString strType = sqliteQueryTypeToString(type); + StrHash< QStringList> groupedTriggers; + + SqliteQueryPtr parsedQuery; + SqliteTableRelatedDdlPtr tableRelatedDdl; + + foreach (QString object, inputList) + { + parsedQuery = getParsedObject(database, object, ANY); + if (!parsedQuery) + { + qWarning() << "Could not get parsed object for " << strType << ":" << object; + continue; + } + + tableRelatedDdl = parsedQuery.dynamicCast(); + if (!tableRelatedDdl) + { + qWarning() << "Parsed object is not of expected type. Expected" << strType + << ", but got" << sqliteQueryTypeToString(parsedQuery->queryType); + continue; + } + + groupedTriggers[tableRelatedDdl->getTargetTable()] << object; + } + + return groupedTriggers; +} + +bool SchemaResolver::isFilteredOut(const QString& value, const QString& type) +{ + if (ignoreSystemObjects) + { + if (type == "table" && isSystemTable(value)) + return true; + + if (type == "index" && isSystemIndex(value, db->getDialect())) + return true; + } + + return false; +} + +QSet SchemaResolver::getDatabases() +{ + return db->getAllAttaches(); +} + +QStringList SchemaResolver::getTableColumns(const QString& table) +{ + return getTableColumns("main", table); +} + +QStringList SchemaResolver::getTableColumns(const QString &database, const QString &table) +{ + QStringList columns; // result + + SqliteQueryPtr query = getParsedObject(database, table, TABLE); + if (!query) + return columns; + + SqliteCreateTablePtr createTable = query.dynamicCast(); + SqliteCreateVirtualTablePtr createVirtualTable = query.dynamicCast(); + if (!createTable && !createVirtualTable) + { + qDebug() << "Parsed DDL is neither a CREATE TABLE or CREATE VIRTUAL TABLE statement. It's: " + << sqliteQueryTypeToString(query->queryType); + + return columns; + } + + // If we parsed virtual table, then we have to create temporary regular table to extract columns. + if (createVirtualTable) + { + createTable = virtualTableAsRegularTable(database, table); + if (!createTable) + return columns; + } + + // Now we have a regular table, let's extract columns. + foreach (SqliteCreateTable::Column* column, createTable->columns) + columns << column->name; + + return columns; +} + +QList SchemaResolver::getTableColumnDataTypes(const QString& table, int expectedNumberOfTypes) +{ + return getTableColumnDataTypes("main", table, expectedNumberOfTypes); +} + +QList SchemaResolver::getTableColumnDataTypes(const QString& database, const QString& table, int expectedNumberOfTypes) +{ + QList dataTypes; + SqliteCreateTablePtr createTable = getParsedObject(database, table, TABLE).dynamicCast(); + if (!createTable) + { + for (int i = 0; i < expectedNumberOfTypes; i++) + dataTypes << DataType(); + + return dataTypes; + } + + for (SqliteCreateTable::Column* col : createTable->columns) + { + if (!col->type) + { + dataTypes << DataType(); + continue; + } + + dataTypes << col->type->toDataType(); + } + + for (int i = dataTypes.size(); i < expectedNumberOfTypes; i++) + dataTypes << DataType(); + + return dataTypes; +} + +StrHash SchemaResolver::getAllTableColumns(const QString &database) +{ + StrHash< QStringList> tableColumns; + foreach (QString table, getTables(database)) + tableColumns[table] = getTableColumns(database, table); + + return tableColumns; +} + +QStringList SchemaResolver::getViewColumns(const QString& view) +{ + return getViewColumns("main", view); +} + +QStringList SchemaResolver::getViewColumns(const QString& database, const QString& view) +{ + QList resolvedColumns = getViewColumnObjects(database, view); + QStringList columns; + foreach (const SelectResolver::Column& col, resolvedColumns) + columns << col.displayName; + + return columns; +} + +QList SchemaResolver::getViewColumnObjects(const QString& view) +{ + return getViewColumnObjects("main", view); +} + +QList SchemaResolver::getViewColumnObjects(const QString& database, const QString& view) +{ + QList results; + SqliteQueryPtr query = getParsedObject(database, view, VIEW); + if (!query) + return results; + + SqliteCreateViewPtr createView = query.dynamicCast(); + if (!createView) + { + qDebug() << "Parsed query is not CREATE VIEW statement as expected."; + return results; + } + + SelectResolver resolver(db, createView->select->detokenize()); + QList > resolvedColumns = resolver.resolve(createView->select); + if (resolvedColumns.size() == 0) + { + qDebug() << "Could not resolve any results column from the view object."; + return results; + } + return resolvedColumns.first(); +} + +SqliteCreateTablePtr SchemaResolver::virtualTableAsRegularTable(const QString &database, const QString &table) +{ + Dialect dialect = db->getDialect(); + QString strippedName = stripObjName(table, dialect); + QString dbName = getPrefixDb(database, dialect); + + // Create temp table to see columns. + QString newTable = db->getUniqueNewObjectName(strippedName); + QString origTable = wrapObjName(strippedName, dialect); + db->exec(QString("CREATE TEMP TABLE %1 AS SELECT * FROM %2.%3 LIMIT 0;").arg(newTable, dbName, origTable), dbFlags); + + // Get parsed DDL of the temp table. + SqliteQueryPtr query = getParsedObject("temp", newTable, TABLE); + if (!query) + return SqliteCreateTablePtr(); + + SqliteCreateTablePtr createTable = query.dynamicCast(); + + // Getting rid of the temp table. + db->exec(QString("DROP TABLE %1;").arg(newTable), dbFlags); + + // Returning results. Might be null. + return createTable; +} + +QString SchemaResolver::getObjectDdl(const QString& name, ObjectType type) +{ + return getObjectDdl("main", name, type); +} + +QString SchemaResolver::getObjectDdl(const QString &database, const QString &name, ObjectType type) +{ + if (name.isNull()) + return QString::null; + + Dialect dialect = db->getDialect(); + // In case of sqlite_master or sqlite_temp_master we have static definitions + QString lowerName = stripObjName(name, dialect).toLower(); + if (lowerName == "sqlite_master") + return getSqliteMasterDdl(false); + else if (lowerName == "sqlite_temp_master") + return getSqliteMasterDdl(true); + + // Prepare db prefix. + QString dbName = getPrefixDb(database, dialect); + + // Get the DDL + QVariant results; + if (type != ANY) + { + results = db->exec(QString( + "SELECT sql FROM %1.sqlite_master WHERE lower(name) = '%2' AND type = '%3';").arg(dbName, escapeString(lowerName), objectTypeToString(type)), + dbFlags + )->getSingleCell(); + } + else + { + results = db->exec(QString( + "SELECT sql FROM %1.sqlite_master WHERE lower(name) = '%2';").arg(dbName, escapeString(lowerName)), + dbFlags + )->getSingleCell(); + } + + // Validate query results + if (!results.isValid() || results.isNull()) + { + qDebug() << "Could not get object's DDL:" << database << "." << name; + return QString::null; + } + + // The DDL string + QString resStr = results.toString(); + + // If the DDL doesn't have semicolon at the end (usually the case), add it. + if (!resStr.trimmed().endsWith(";")) + resStr += ";"; + + // Return the DDL + return resStr; +} + +SqliteQueryPtr SchemaResolver::getParsedObject(const QString &name, ObjectType type) +{ + return getParsedObject("main", name, type); +} + +SqliteQueryPtr SchemaResolver::getParsedObject(const QString &database, const QString &name, ObjectType type) +{ + // Get DDL + QString ddl = getObjectDdl(database, name, type); + if (ddl.isNull()) + return SqliteQueryPtr(); + + // Parse DDL + return getParsedDdl(ddl); +} + +StrHash< SqliteQueryPtr> SchemaResolver::getAllParsedObjects() +{ + return getAllParsedObjects("main"); +} + +StrHash< SqliteQueryPtr> SchemaResolver::getAllParsedObjects(const QString& database) +{ + return getAllParsedObjectsForType(database, QString::null); +} + +StrHash< SqliteCreateTablePtr> SchemaResolver::getAllParsedTables() +{ + return getAllParsedTables("main"); +} + +StrHash< SqliteCreateTablePtr> SchemaResolver::getAllParsedTables(const QString& database) +{ + return getAllParsedObjectsForType(database, "table"); +} + +StrHash< SqliteCreateIndexPtr> SchemaResolver::getAllParsedIndexes() +{ + return getAllParsedIndexes("main"); +} + +StrHash< SqliteCreateIndexPtr> SchemaResolver::getAllParsedIndexes(const QString& database) +{ + return getAllParsedObjectsForType(database, "index"); +} + +StrHash< SqliteCreateTriggerPtr> SchemaResolver::getAllParsedTriggers() +{ + return getAllParsedTriggers("main"); +} + +StrHash< SqliteCreateTriggerPtr> SchemaResolver::getAllParsedTriggers(const QString& database) +{ + return getAllParsedObjectsForType(database, "trigger"); +} + +StrHash< SqliteCreateViewPtr> SchemaResolver::getAllParsedViews() +{ + return getAllParsedViews("main"); +} + +StrHash< SqliteCreateViewPtr> SchemaResolver::getAllParsedViews(const QString& database) +{ + return getAllParsedObjectsForType(database, "view"); +} + +SqliteQueryPtr SchemaResolver::getParsedDdl(const QString& ddl) +{ + if (!parser->parse(ddl)) + { + qDebug() << "Could not parse DDL for parsing object by SchemaResolver. Errors are:"; + foreach (ParserError* err, parser->getErrors()) + qDebug() << err->getMessage(); + + return SqliteQueryPtr(); + } + + // Validate parsed DDL + QList queries = parser->getQueries(); + if (queries.size() == 0) + { + qDebug() << "No parsed query while getting temp table columns."; + return SqliteQueryPtr(); + } + + // Preparing results + return queries[0]; +} + +QStringList SchemaResolver::getObjects(const QString &type) +{ + return getObjects(QString::null, type); +} + +QStringList SchemaResolver::getObjects(const QString &database, const QString &type) +{ + QStringList resList; + QString dbName = getPrefixDb(database, db->getDialect()); + + SqlQueryPtr results = db->exec(QString("SELECT name FROM %1.sqlite_master WHERE type = ?;").arg(dbName), {type}, dbFlags); + + QString value; + foreach (SqlResultsRowPtr row, results->getAll()) + { + value = row->value(0).toString(); + if (!isFilteredOut(value, type)) + resList << value; + } + + return resList; +} + +QStringList SchemaResolver::getAllObjects() +{ + return getAllObjects(QString::null); +} + +QStringList SchemaResolver::getAllObjects(const QString& database) +{ + QStringList resList; + QString dbName = getPrefixDb(database, db->getDialect()); + + SqlQueryPtr results = db->exec(QString("SELECT name, type FROM %1.sqlite_master;").arg(dbName), dbFlags); + + QString value; + QString type; + foreach (SqlResultsRowPtr row, results->getAll()) + { + value = row->value("name").toString(); + type = row->value("type").toString(); + if (!isFilteredOut(value, type)) + resList << value; + } + + return resList; +} + +QString SchemaResolver::getUniqueName(const QString& database, const QString& namePrefix) +{ + QStringList allObjects = getAllObjects(database); + QString baseName = namePrefix; + QString name = baseName; + for (int i = 0; allObjects.contains(name); i++) + name = baseName + QString::number(i); + + return name; +} + +QString SchemaResolver::getUniqueName(const QString& namePrefix) +{ + return getUniqueName("main", namePrefix); +} + +QStringList SchemaResolver::getFkReferencingTables(const QString& table) +{ + return getFkReferencingTables("main", table); +} + +QStringList SchemaResolver::getFkReferencingTables(const QString& database, const QString& table) +{ + Dialect dialect = db->getDialect(); + if (dialect == Dialect::Sqlite2) + return QStringList(); + + // Get all tables + StrHash parsedTables = getAllParsedTables(database); + + // Exclude queried table from the list + parsedTables.remove(table); + + // Resolve referencing tables + return getFkReferencingTables(table, parsedTables.values()); +} + +QStringList SchemaResolver::getFkReferencingTables(const QString& table, const QList& allParsedTables) +{ + QStringList tables; + + QList tableFks; + QList fks; + bool result = false; + for (SqliteCreateTablePtr createTable : allParsedTables) + { + // Check table constraints + tableFks = createTable->getForeignKeysByTable(table); + result = contains(tableFks, [&table](SqliteCreateTable::Constraint* fk) + { + return fk->foreignKey->foreignTable == table; + }); + + if (result) + { + tables << createTable->table; + continue; + } + + // Check column constraints + for (SqliteCreateTable::Column* column : createTable->columns) + { + fks = column->getForeignKeysByTable(table); + result = contains(fks, [&table](SqliteCreateTable::Column::Constraint* fk) + { + return fk->foreignKey->foreignTable == table; + }); + + if (result) + { + tables << createTable->table; + break; + } + } + } + + return tables; +} + +QStringList SchemaResolver::getIndexesForTable(const QString& database, const QString& table) +{ + QStringList names; + foreach (SqliteCreateIndexPtr idx, getParsedIndexesForTable(database, table)) + names << idx->index; + + return names; +} + +QStringList SchemaResolver::getIndexesForTable(const QString& table) +{ + return getIndexesForTable("main", table); +} + +QStringList SchemaResolver::getTriggersForTable(const QString& database, const QString& table) +{ + QStringList names; + foreach (SqliteCreateTriggerPtr trig, getParsedTriggersForTable(database, table)) + names << trig->trigger; + + return names; +} + +QStringList SchemaResolver::getTriggersForTable(const QString& table) +{ + return getTriggersForTable("main", table); +} + +QStringList SchemaResolver::getTriggersForView(const QString& database, const QString& view) +{ + QStringList names; + foreach (SqliteCreateTriggerPtr trig, getParsedTriggersForView(database, view)) + names << trig->trigger; + + return names; +} + +QStringList SchemaResolver::getTriggersForView(const QString& view) +{ + return getTriggersForView("main", view); +} + +QStringList SchemaResolver::getViewsForTable(const QString& database, const QString& table) +{ + QStringList names; + foreach (SqliteCreateViewPtr view, getParsedViewsForTable(database, table)) + names << view->view; + + return names; +} + +QStringList SchemaResolver::getViewsForTable(const QString& table) +{ + return getViewsForTable("main", table); +} + +StrHash SchemaResolver::getAllObjectDetails() +{ + return getAllObjectDetails("main"); +} + +StrHash SchemaResolver::getAllObjectDetails(const QString& database) +{ + StrHash< ObjectDetails> details; + ObjectDetails detail; + QString type; + + SqlQueryPtr results = db->exec(QString("SELECT name, type, sql FROM %1.sqlite_master").arg(getPrefixDb(database, db->getDialect())), dbFlags); + if (results->isError()) + { + qCritical() << "Error while getting all object details in SchemaResolver:" << results->getErrorCode(); + return details; + } + + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + type = row->value("type").toString(); + detail.type = stringToObjectType(type); + if (detail.type == ANY) + qCritical() << "Unhlandled db object type:" << type; + + detail.ddl = row->value("sql").toString(); + details[row->value("name").toString()] = detail; + } + return details; +} + +QList SchemaResolver::getParsedIndexesForTable(const QString& database, const QString& table) +{ + QList createIndexList; + + QStringList indexes = getIndexes(database); + SqliteQueryPtr query; + SqliteCreateIndexPtr createIndex; + foreach (const QString& index, indexes) + { + query = getParsedObject(database, index, INDEX); + if (!query) + continue; + + createIndex = query.dynamicCast(); + if (!createIndex) + { + qWarning() << "Parsed DDL was not a CREATE INDEX statement, while queried for indexes."; + continue; + } + + if (createIndex->table.compare(table, Qt::CaseInsensitive) == 0) + createIndexList << createIndex; + } + return createIndexList; +} + +QList SchemaResolver::getParsedIndexesForTable(const QString& table) +{ + return getParsedIndexesForTable("main", table); +} + +QList SchemaResolver::getParsedTriggersForTable(const QString& database, const QString& table, bool includeContentReferences) +{ + return getParsedTriggersForTableOrView(database, table, includeContentReferences, true); +} + +QList SchemaResolver::getParsedTriggersForTable(const QString& table, bool includeContentReferences) +{ + return getParsedTriggersForTable("main", table, includeContentReferences); +} + +QList SchemaResolver::getParsedTriggersForView(const QString& database, const QString& view, bool includeContentReferences) +{ + return getParsedTriggersForTableOrView(database, view, includeContentReferences, false); +} + +QList SchemaResolver::getParsedTriggersForView(const QString& view, bool includeContentReferences) +{ + return getParsedTriggersForView("main", view, includeContentReferences); +} + +QList SchemaResolver::getParsedTriggersForTableOrView(const QString& database, const QString& tableOrView, + bool includeContentReferences, bool table) +{ + QList createTriggerList; + + QStringList triggers = getTriggers(database); + SqliteQueryPtr query; + SqliteCreateTriggerPtr createTrigger; + foreach (const QString& trig, triggers) + { + query = getParsedObject(database, trig, TRIGGER); + if (!query) + continue; + + createTrigger = query.dynamicCast(); + if (!createTrigger) + { + qWarning() << "Parsed DDL was not a CREATE TRIGGER statement, while queried for triggers." << createTrigger.data(); + continue; + } + + // The condition below checks: + // 1. if this is a call for table triggers and event time is INSTEAD_OF - skip this iteration + // 2. if this is a call for view triggers and event time is _not_ INSTEAD_OF - skip this iteration + // In other words, it's a logical XOR for "table" flag and "eventTime == INSTEAD_OF" condition. + if (table == (createTrigger->eventTime == SqliteCreateTrigger::Time::INSTEAD_OF)) + continue; + + if (createTrigger->table.compare(tableOrView, Qt::CaseInsensitive) == 0) + createTriggerList << createTrigger; + else if (includeContentReferences && indexOf(createTrigger->getContextTables(), tableOrView, Qt::CaseInsensitive) > -1) + createTriggerList << createTrigger; + + } + return createTriggerList; +} + +QString SchemaResolver::objectTypeToString(SchemaResolver::ObjectType type) +{ + switch (type) + { + case TABLE: + return "table"; + case INDEX: + return "index"; + case TRIGGER: + return "trigger"; + case VIEW: + return "view"; + case ANY: + return QString(); + } + return QString(); +} + +SchemaResolver::ObjectType SchemaResolver::stringToObjectType(const QString& type) +{ + if (type == "table") + return SchemaResolver::TABLE; + else if (type == "index") + return SchemaResolver::INDEX; + else if (type == "trigger") + return SchemaResolver::TRIGGER; + else if (type == "view") + return SchemaResolver::VIEW; + else + return SchemaResolver::ANY; +} + +QList SchemaResolver::getParsedViewsForTable(const QString& database, const QString& table) +{ + QList createViewList; + + QStringList views = getViews(database); + SqliteQueryPtr query; + SqliteCreateViewPtr createView; + foreach (const QString& view, views) + { + query = getParsedObject(database, view, VIEW); + if (!query) + continue; + + createView = query.dynamicCast(); + if (!createView) + { + qWarning() << "Parsed DDL was not a CREATE VIEW statement, while queried for views."; + continue; + } + + if (indexOf(createView->getContextTables(), table, Qt::CaseInsensitive) > -1) + createViewList << createView; + } + return createViewList; +} + +QList SchemaResolver::getParsedViewsForTable(const QString& table) +{ + return getParsedViewsForTable("main", table); +} + +void SchemaResolver::filterSystemIndexes(QStringList& indexes) +{ + Dialect dialect = db->getDialect(); + QMutableListIterator it(indexes); + while (it.hasNext()) + { + if (isSystemIndex(it.next(), dialect)) + it.remove(); + } +} + +bool SchemaResolver::isWithoutRowIdTable(const QString& table) +{ + return isWithoutRowIdTable("main", table); +} + +bool SchemaResolver::isWithoutRowIdTable(const QString& database, const QString& table) +{ + SqliteQueryPtr query = getParsedObject(database, table, TABLE); + if (!query) + return false; + + SqliteCreateTablePtr createTable = query.dynamicCast(); + if (!createTable) + return false; + + return !createTable->withOutRowId.isNull(); +} + +bool SchemaResolver::isVirtualTable(const QString& database, const QString& table) +{ + SqliteQueryPtr query = getParsedObject(database, table, TABLE); + if (!query) + return false; + + SqliteCreateVirtualTablePtr createVirtualTable = query.dynamicCast(); + return !createVirtualTable.isNull(); +} + +bool SchemaResolver::isVirtualTable(const QString& table) +{ + return isVirtualTable("main", table); +} + +SqliteCreateTablePtr SchemaResolver::resolveVirtualTableAsRegularTable(const QString& table) +{ + return resolveVirtualTableAsRegularTable("maine", table); +} + +SqliteCreateTablePtr SchemaResolver::resolveVirtualTableAsRegularTable(const QString& database, const QString& table) +{ + return virtualTableAsRegularTable(database, table); +} + +QStringList SchemaResolver::getWithoutRowIdTableColumns(const QString& table) +{ + return getWithoutRowIdTableColumns("main", table); +} + +QStringList SchemaResolver::getWithoutRowIdTableColumns(const QString& database, const QString& table) +{ + QStringList columns; + + SqliteQueryPtr query = getParsedObject(database, table, TABLE); + if (!query) + return columns; + + SqliteCreateTablePtr createTable = query.dynamicCast(); + if (!createTable) + return columns; + + if (createTable->withOutRowId.isNull()) + return columns; // it's not WITHOUT ROWID table + + return createTable->getPrimaryKeyColumns(); +} + +QString SchemaResolver::getSqliteMasterDdl(bool temp) +{ + if (temp) + return sqliteTempMasterDdl; + + return sqliteMasterDdl; +} + +QStringList SchemaResolver::getCollations() +{ + QStringList list; + if (db->getDialect() != Dialect::Sqlite3) + return list; + + SqlQueryPtr results = db->exec("PRAGMA collation_list", dbFlags); + if (results->isError()) + { + qWarning() << "Could not read collation list from the database:" << results->getErrorText(); + return list; + } + + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + list << row->value("name").toString(); + } + + return list; +} + +bool SchemaResolver::getIgnoreSystemObjects() const +{ + return ignoreSystemObjects; +} + +void SchemaResolver::setIgnoreSystemObjects(bool value) +{ + ignoreSystemObjects = value; +} + +bool SchemaResolver::getNoDbLocking() const +{ + return dbFlags.testFlag(Db::Flag::NO_LOCK); +} + +void SchemaResolver::setNoDbLocking(bool value) +{ + if (value) + dbFlags |= Db::Flag::NO_LOCK; + else + dbFlags ^= Db::Flag::NO_LOCK; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h new file mode 100644 index 0000000..e1a8d5d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h @@ -0,0 +1,220 @@ +#ifndef SCHEMARESOLVER_H +#define SCHEMARESOLVER_H + +#include "parser/parser.h" +#include "parser/ast/sqlitequerytype.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "coreSQLiteStudio_global.h" +#include "common/utils_sql.h" +#include "selectresolver.h" +#include "db/sqlresultsrow.h" +#include "db/sqlquery.h" +#include "db/db.h" +#include "common/strhash.h" +#include + +class SqliteCreateTable; + +// TODO add cache + +class API_EXPORT SchemaResolver +{ + public: + enum ObjectType + { + TABLE, + INDEX, + TRIGGER, + VIEW, + ANY + }; + + struct ObjectDetails + { + ObjectType type; + QString ddl; + }; + + explicit SchemaResolver(Db* db); + virtual ~SchemaResolver(); + + QStringList getTables(const QString& database = QString::null); + QStringList getIndexes(const QString& database = QString::null); + QStringList getTriggers(const QString& database = QString::null); + QStringList getViews(const QString& database = QString::null); + StrHash getGroupedIndexes(const QString& database = QString::null); + StrHash getGroupedTriggers(const QString& database = QString::null); + QSet getDatabases(); + QStringList getObjects(const QString& type); + QStringList getObjects(const QString& database, const QString& type); + QStringList getAllObjects(); + QStringList getAllObjects(const QString& database); + QString getUniqueName(const QString& database, const QString& namePrefix); + QString getUniqueName(const QString& namePrefix = QString::null); + QStringList getFkReferencingTables(const QString& table); + QStringList getFkReferencingTables(const QString& database, const QString& table); + + QStringList getIndexesForTable(const QString& database, const QString& table); + QStringList getIndexesForTable(const QString& table); + QStringList getTriggersForTable(const QString& database, const QString& table); + QStringList getTriggersForTable(const QString& table); + QStringList getTriggersForView(const QString& database, const QString& view); + QStringList getTriggersForView(const QString& view); + QStringList getViewsForTable(const QString& database, const QString& table); + QStringList getViewsForTable(const QString& table); + + StrHash getAllObjectDetails(); + StrHash getAllObjectDetails(const QString& database); + + QList getParsedIndexesForTable(const QString& database, const QString& table); + QList getParsedIndexesForTable(const QString& table); + QList getParsedTriggersForTable(const QString& database, const QString& table, bool includeContentReferences = false); + QList getParsedTriggersForTable(const QString& table, bool includeContentReferences = false); + QList getParsedTriggersForView(const QString& database, const QString& view, bool includeContentReferences = false); + QList getParsedTriggersForView(const QString& view, bool includeContentReferences = false); + QList getParsedViewsForTable(const QString& database, const QString& table); + QList getParsedViewsForTable(const QString& table); + + bool isWithoutRowIdTable(const QString& table); + bool isWithoutRowIdTable(const QString& database, const QString& table); + bool isVirtualTable(const QString& database, const QString& table); + bool isVirtualTable(const QString& table); + SqliteCreateTablePtr resolveVirtualTableAsRegularTable(const QString& table); + SqliteCreateTablePtr resolveVirtualTableAsRegularTable(const QString& database, const QString& table); + + QStringList getWithoutRowIdTableColumns(const QString& table); + QStringList getWithoutRowIdTableColumns(const QString& database, const QString& table); + + /** + * @brief getTableColumns Get column names for a table. + * @param table Table to query. + * @return List of column names of the table. + * This does something similar to getting list of columns with + * PRAGMA table_info(), but the pragma in Sqlite2 doesn't support + * queries for attached databases, therefore this method is provided + * to make this possible. It finds table's DDL and parses it, + * then extracts list of columns from parsing results. + */ + QStringList getTableColumns(const QString& table); + + /** + * @brief getTableColumns Get column names for a table. + * @param database Attached database name. + * @param table Table to query. + * @return List of column names of the table. + * @overload + */ + QStringList getTableColumns(const QString& database, const QString& table); + + QList getTableColumnDataTypes(const QString& table, int expectedNumberOfTypes = -1); + QList getTableColumnDataTypes(const QString& database, const QString& table, int expectedNumberOfTypes = -1); + + StrHash getAllTableColumns(const QString& database = QString::null); + + QStringList getViewColumns(const QString& view); + QStringList getViewColumns(const QString& database, const QString& view); + QList getViewColumnObjects(const QString& view); + QList getViewColumnObjects(const QString& database, const QString& view); + + QString getObjectDdl(const QString& name, ObjectType type); + QString getObjectDdl(const QString& database, const QString& name, ObjectType type); + + /** + * @brief Parses given object's DDL. + * @param name Name of the object in the database. + * @return Parsed object, or null pointer if named object was not in the database, or parsing error occured. + * + * Returned query has to be deleted outside! + */ + SqliteQueryPtr getParsedObject(const QString& name, ObjectType type); + + /** + * @brief Parses given object's DDL. + * @param database Database that the object is in (the attach name of the database). + * @param name Name of the object in the database. + * @return Parsed object, or null pointer if named object was not in the database, or parsing error occured. + * @overload + */ + SqliteQueryPtr getParsedObject(const QString& database, const QString& name, ObjectType type); + + StrHash getAllParsedObjects(); + StrHash getAllParsedObjects(const QString& database); + StrHash getAllParsedTables(); + StrHash getAllParsedTables(const QString& database); + StrHash getAllParsedIndexes(); + StrHash getAllParsedIndexes(const QString& database); + StrHash getAllParsedTriggers(); + StrHash getAllParsedTriggers(const QString& database); + StrHash getAllParsedViews(); + StrHash getAllParsedViews(const QString& database); + + static QString getSqliteMasterDdl(bool temp = false); + static QStringList getFkReferencingTables(const QString& table, const QList& allParsedTables); + + QStringList getCollations(); + + bool getIgnoreSystemObjects() const; + void setIgnoreSystemObjects(bool value); + + bool getNoDbLocking() const; + void setNoDbLocking(bool value); + + static QString objectTypeToString(ObjectType type); + static ObjectType stringToObjectType(const QString& type); + + private: + SqliteQueryPtr getParsedDdl(const QString& ddl); + SqliteCreateTablePtr virtualTableAsRegularTable(const QString& database, const QString& table); + StrHash< QStringList> getGroupedObjects(const QString &database, const QStringList& inputList, SqliteQueryType type); + bool isFilteredOut(const QString& value, const QString& type); + void filterSystemIndexes(QStringList& indexes); + QList getParsedTriggersForTableOrView(const QString& database, const QString& tableOrView, bool includeContentReferences, bool table); + + template + StrHash> getAllParsedObjectsForType(const QString& database, const QString& type); + + Db* db = nullptr; + Parser* parser = nullptr; + bool ignoreSystemObjects = false; + Db::Flags dbFlags; +}; + +template +StrHash> SchemaResolver::getAllParsedObjectsForType(const QString& database, const QString& type) +{ + StrHash< QSharedPointer> parsedObjects; + + QString dbName = getPrefixDb(database, db->getDialect()); + + SqlQueryPtr results; + + if (type.isNull()) + results = db->exec(QString("SELECT name, type, sql FROM %1.sqlite_master;").arg(dbName)); + else + results = db->exec(QString("SELECT name, type, sql FROM %1.sqlite_master WHERE type = '%2';").arg(dbName, type)); + + QString name; + SqliteQueryPtr parsedObject; + QSharedPointer castedObject; + foreach (SqlResultsRowPtr row, results->getAll()) + { + name = row->value("name").toString(); + parsedObject = getParsedDdl(row->value("sql").toString()); + if (!parsedObject) + continue; + + if (isFilteredOut(name, row->value("type").toString())) + continue; + + castedObject = parsedObject.dynamicCast(); + if (castedObject) + parsedObjects[name] = castedObject; + } + + return parsedObjects; +} + +#endif // SCHEMARESOLVER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp new file mode 100644 index 0000000..9e425a2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp @@ -0,0 +1,673 @@ +#include "selectresolver.h" +#include "parser/token.h" +#include "parser/lexer.h" +#include "parser/keywords.h" +#include "schemaresolver.h" +#include "parser/ast/sqlitecreateview.h" +#include "common/global.h" +#include +#include +#include +#include + +SelectResolver::SelectResolver(Db *db, const QString& originalQuery) +{ + this->db = db; + this->query = originalQuery; + schemaResolver = new SchemaResolver(db); +} + +SelectResolver::SelectResolver(Db* db, const QString& originalQuery, const BiStrHash& dbNameToAttach) : + SelectResolver(db, originalQuery) +{ + this->dbNameToAttach = dbNameToAttach; +} + +SelectResolver::~SelectResolver() +{ + safe_delete(schemaResolver); +} + +QList SelectResolver::resolve(SqliteSelect::Core *selectCore) +{ + errors.clear(); + return resolveCore(selectCore); +} + +QList > SelectResolver::resolve(SqliteSelect *select) +{ + errors.clear(); + QList > results; + foreach (SqliteSelect::Core* core, select->coreSelects) + { + results << resolveCore(core); + currentCoreResults.clear(); + } + + return results; +} + +QList SelectResolver::resolveAvailableColumns(SqliteSelect::Core *selectCore) +{ + errors.clear(); + return resolveAvailableCoreColumns(selectCore); +} + +QList > SelectResolver::resolveAvailableColumns(SqliteSelect *select) +{ + errors.clear(); + QList > results; + foreach (SqliteSelect::Core* core, select->coreSelects) + results << resolveAvailableCoreColumns(core); + + return results; +} + +QSet SelectResolver::resolveTables(SqliteSelect::Core *selectCore) +{ + QSet
tables; + QList columns = resolveAvailableColumns(selectCore); + foreach (Column col, columns) + tables << col.getTable(); + + return tables; +} + +QList > SelectResolver::resolveTables(SqliteSelect *select) +{ + QList > results; + QList > columnLists = resolveAvailableColumns(select); + foreach (QList columns, columnLists) + { + QSet
tables; + foreach (Column col, columns) + tables << col.getTable(); + + results << tables; + } + + return results; +} + +QList SelectResolver::translateToColumns(SqliteSelect* select, const TokenList& columnTokens) +{ + errors.clear(); + QList results; + foreach (TokenPtr token, columnTokens) + results << translateTokenToColumn(select, token); + + return results; +} + +SelectResolver::Column SelectResolver::translateToColumns(SqliteSelect* select, TokenPtr token) +{ + errors.clear(); + return translateTokenToColumn(select, token); +} + +bool SelectResolver::hasErrors() const +{ + return !errors.isEmpty(); +} + +const QStringList& SelectResolver::getErrors() const +{ + return errors; +} + +QList SelectResolver::resolveCore(SqliteSelect::Core* selectCore) +{ + if (selectCore->from) + currentCoreSourceColumns = resolveJoinSource(selectCore->from); + + foreach (SqliteSelect::Core::ResultColumn* resCol, selectCore->resultColumns) + resolve(resCol); + + if (selectCore->distinctKw) + markDistinctColumns(); + + if (selectCore->groupBy.size() > 0) + markGroupedColumns(); + + fixColumnNames(); + + SqliteSelect* select = dynamic_cast(selectCore->parentStatement()); + if (select && select->coreSelects.size() > 1) + markCompoundColumns(); + + if (select && select->with) + markCteColumns(); + + return currentCoreResults; +} + +QList SelectResolver::resolveAvailableCoreColumns(SqliteSelect::Core* selectCore) +{ + QList columns; + if (selectCore->from) + columns = resolveJoinSource(selectCore->from); + + SqliteSelect* select = dynamic_cast(selectCore->parentStatement()); + if (select && select->with) + markCteColumns(); + + return columns; +} + +SelectResolver::Column SelectResolver::translateTokenToColumn(SqliteSelect* select, TokenPtr token) +{ + // Default result + Column notTranslatedColumn; + notTranslatedColumn.type = Column::OTHER; + notTranslatedColumn.column = token->value; + + // Find containing statement + SqliteStatement* parentStmt = select->findStatementWithToken(token); + if (!parentStmt) + { + qDebug() << "Could not find containing statement for given token while translating column token:" << token->toString() + << "Select tokens:" << select->tokens.toString(); + + return notTranslatedColumn; + } + + // Go through all select cores, from the most deep, to the most shallow + SqliteSelect::Core* core = nullptr; + while (parentStmt) + { + // Find nearest SELECT core. + while (parentStmt && !(core = dynamic_cast(parentStmt))) + parentStmt = parentStmt->parentStatement(); + + if (!core) + { + qDebug() << "Could not find SqliteSelect::Core object for given token while translating column token:" << token->toString() + << "Select:" << select->detokenize(); + + return notTranslatedColumn; + } + + // Search through available columns + foreach (const Column& availableColumn, resolveAvailableColumns(core)) + { + if (availableColumn.type == Column::COLUMN && availableColumn.column.compare(token->value, Qt::CaseInsensitive) == 0) + return availableColumn; + } + + // Not in this core. See if there is any core upper (if this was a subselect). + parentStmt = parentStmt->parentStatement(); + } + + return notTranslatedColumn; +} + +void SelectResolver::markDistinctColumns() +{ + markCurrentColumnsWithFlag(FROM_DISTINCT_SELECT); +} + +void SelectResolver::markCompoundColumns() +{ + markCurrentColumnsWithFlag(FROM_COMPOUND_SELECT); +} + +void SelectResolver::markCteColumns() +{ + markCurrentColumnsWithFlag(FROM_CTE_SELECT); +} + +void SelectResolver::markGroupedColumns() +{ + markCurrentColumnsWithFlag(FROM_GROUPED_SELECT); +} + +void SelectResolver::fixColumnNames() +{ + QSet existingDisplayNames; + QString originalName; + int i; + + QMutableListIterator it(currentCoreResults); + while (it.hasNext()) + { + originalName = it.next().displayName; + for (i = 1; existingDisplayNames.contains(it.value().displayName); i++) + it.value().displayName = originalName + ":" + QString::number(i); + + existingDisplayNames << it.value().displayName; + } +} + +void SelectResolver::markCurrentColumnsWithFlag(SelectResolver::Flag flag) +{ + QMutableListIterator it(currentCoreResults); + while (it.hasNext()) + it.next().flags |= flag; +} + +void SelectResolver::resolve(SqliteSelect::Core::ResultColumn *resCol) +{ + if (resCol->star) + resolveStar(resCol); + else + resolveExpr(resCol); +} + +void SelectResolver::resolveStar(SqliteSelect::Core::ResultColumn *resCol) +{ + bool foundAtLeastOne = false; + foreach (SelectResolver::Column column, currentCoreSourceColumns) + { + if (!resCol->table.isNull()) + { + /* + * Star was prefixed with table or table alias. + * The "FROM" clause allows to use alias name the same as + * some other table real name in the very same "FROM". + * Their columns concatenate, so here we allow any column that + * prefix matches either alias or table from data source list. + * For example it's correct to query: + * SELECT test.* FROM test, otherTable AS test; + * This case is simpler then in resolveDbAndTable(), + * because here's no database allowed. + * + * Also, if the table has an alias specified, + * then the alias has a precedence before table's name, + * therefore we match table name only if the table alias + * is null. + */ + if ( + ( + !column.tableAlias.isNull() && + resCol->table.compare(column.tableAlias, Qt::CaseInsensitive) != 0 + ) || + ( + column.tableAlias.isNull() && + resCol->table.compare(column.table, Qt::CaseInsensitive) != 0 + ) + + ) + { + continue; + } + } + + // If source column name is aliased, use it + if (!column.alias.isNull()) + column.displayName = column.alias; + else + column.displayName = column.column; + + column.originalColumn = resCol; + currentCoreResults << column; + foundAtLeastOne = true; + } + + if (!foundAtLeastOne) + errors << QObject::tr("Could not resolve data source for column: %1").arg(resCol->detokenize()); +} + +void SelectResolver::resolveExpr(SqliteSelect::Core::ResultColumn *resCol) +{ + SelectResolver::Column column; + column.alias = resCol->alias; + column.originalColumn = resCol; + column.column = getResColTokensWithoutAlias(resCol).detokenize().trimmed(); + column.displayName = !resCol->alias.isNull() ? column.alias : column.column; + + SqliteExpr* expr = resCol->expr; + if (expr->mode != SqliteExpr::Mode::ID) + { + // Not a simple column, but some expression + column.type = Column::OTHER; + currentCoreResults << column; + + // In this case we end it here. + return; + } + + // Now we know we're dealing with db.table.column (with db and table optional) + resolveDbAndTable(resCol); +} + +void SelectResolver::resolveDbAndTable(SqliteSelect::Core::ResultColumn *resCol) +{ + SqliteExpr* expr = resCol->expr; + + // Basic info + Column col; + col.alias = resCol->alias; + col.column = expr->column; + col.originalColumn = resCol; + col.type = Column::COLUMN; + + // Display name + if (col.alias.isNull()) + col.displayName = expr->column; + else + col.displayName = col.alias; + + // Looking for table relation + Column matched; + if (isRowIdKeyword(expr->column)) + matched = resolveRowIdColumn(expr); + else if (!expr->database.isNull()) + matched = resolveExplicitColumn(expr->database, expr->table, expr->column); + else if (!expr->table.isNull()) + matched = resolveExplicitColumn(expr->table, expr->column); + else + matched = resolveExplicitColumn(expr->column); + + + if (!matched.table.isNull()) + { + col.database = matched.database; + col.originalDatabase = resolveDatabase(matched.database); + col.table = matched.table; + col.tableAlias = matched.tableAlias; + col.flags = matched.flags; + } + else if (!ignoreInvalidNames) + { + qDebug() << "Source table for column '" << expr->detokenize() + << "' not matched while resolving select: " << query; + } + + currentCoreResults << col; +} + +SelectResolver::Column SelectResolver::resolveRowIdColumn(SqliteExpr *expr) +{ + // Looking for first source that can provide ROWID. + foreach (Column column, currentCoreSourceColumns) + { + if (column.table.isNull()) + continue; // ROWID cannot be related to source with no table + + if (!expr->table.isNull() && matchTable(column, expr->table)) + return column; + } + return Column(); +} + +SelectResolver::Column SelectResolver::resolveExplicitColumn(const QString &columnName) +{ + foreach (const Column& column, currentCoreSourceColumns) + { + if (columnName.compare(column.column, Qt::CaseInsensitive) != 0 && columnName.compare(column.alias, Qt::CaseInsensitive) != 0) + continue; + + return column; + } + return Column(); +} + +SelectResolver::Column SelectResolver::resolveExplicitColumn(const QString &table, const QString &columnName) +{ + foreach (const Column& column, currentCoreSourceColumns) + { + if (columnName.compare(column.column, Qt::CaseInsensitive) != 0 && columnName.compare(column.alias, Qt::CaseInsensitive) != 0) + continue; + + if (!matchTable(column, table)) + continue; + + return column; + } + return Column(); +} + +SelectResolver::Column SelectResolver::resolveExplicitColumn(const QString &database, const QString &table, const QString &columnName) +{ + foreach (const Column& column, currentCoreSourceColumns) + { + if (columnName.compare(column.column, Qt::CaseInsensitive) != 0 && columnName.compare(column.alias, Qt::CaseInsensitive) != 0) + continue; + + if (!matchTable(column, table)) + continue; + + if (database.compare(column.database, Qt::CaseInsensitive) != 0) + continue; + + return column; + } + return Column(); +} + +bool SelectResolver::matchTable(const SelectResolver::Column &sourceColumn, const QString &table) +{ + // First check by tableAlias if it's present + if (!sourceColumn.tableAlias.isNull()) + return (sourceColumn.tableAlias.compare(table, Qt::CaseInsensitive) == 0); + + return (sourceColumn.table.compare(table, Qt::CaseInsensitive) == 0); +} + +TokenList SelectResolver::getResColTokensWithoutAlias(SqliteSelect::Core::ResultColumn *resCol) +{ + TokenList allTokens = resCol->tokens; + if (!resCol->alias.isNull()) + { + int depth = 0; + int idx = -1; + int idxCandidate = -1; + for (const TokenPtr& token : allTokens) + { + idxCandidate++; + if (token->type == Token::PAR_LEFT) + { + depth++; + } + else if (token->type == Token::PAR_RIGHT) + { + depth--; + } + else if (token->type == Token::KEYWORD && token->value.compare("AS", Qt::CaseInsensitive) && depth <= 0) + { + idx = idxCandidate; + break; + } + } + + if (idx > -1) + allTokens = allTokens.mid(0, idx - 1); + } + + return allTokens; +} + +QList SelectResolver::resolveJoinSource(SqliteSelect::Core::JoinSource *joinSrc) +{ + QList columnSources; + columnSources += resolveSingleSource(joinSrc->singleSource); + foreach (SqliteSelect::Core::JoinSourceOther* otherSrc, joinSrc->otherSources) + columnSources += resolveOtherSource(otherSrc); + + return columnSources; +} + +QList SelectResolver::resolveSingleSource(SqliteSelect::Core::SingleSource *joinSrc) +{ + if (!joinSrc) + return QList(); + + if (joinSrc->select) + return resolveSingleSourceSubSelect(joinSrc); + + if (joinSrc->joinSource) + return resolveJoinSource(joinSrc->joinSource); + + if (isView(joinSrc->database, joinSrc->table)) + return resolveView(joinSrc->database, joinSrc->table, joinSrc->alias); + + QList columnSources; + QStringList columns = getTableColumns(joinSrc->database, joinSrc->table, joinSrc->alias); + Column column; + foreach (QString columnName, columns) + { + column.type = Column::COLUMN; + column.column = columnName; + column.table = joinSrc->table;; + column.database = joinSrc->database; + column.originalDatabase = resolveDatabase(joinSrc->database); + if (!joinSrc->alias.isNull()) + column.tableAlias = joinSrc->alias; + + columnSources << column; + } + + return columnSources; +} + +QList SelectResolver::resolveSingleSourceSubSelect(SqliteSelect::Core::SingleSource *joinSrc) +{ + QList columnSources = resolveSubSelect(joinSrc->select); + applySubSelectAlias(columnSources, joinSrc->alias); + return columnSources; +} + +QList SelectResolver::resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc) +{ + return resolveSingleSource(otherSrc->singleSource); +} + +QList SelectResolver::resolveSubSelect(SqliteSelect *select) +{ + QList columnSources; + Q_ASSERT(select->coreSelects.size() > 0); + + bool compound = (select->coreSelects.size() > 1); + + if (compound && !resolveMultiCore) + return columnSources; + + SelectResolver internalResolver(db, query); + columnSources += internalResolver.resolve(select->coreSelects[0]); + + if (compound) + { + QMutableListIterator it(columnSources); + while (it.hasNext()) + it.next().flags |= FROM_COMPOUND_SELECT; + } + + return columnSources; +} + +QList SelectResolver::resolveView(const QString& database, const QString& name, const QString& alias) +{ + QList results; + SqliteQueryPtr query = schemaResolver->getParsedObject(database, name, SchemaResolver::VIEW); + if (!query) + { + qDebug() << "Could not get parsed CREATE VIEW in SelectResolver::resolveView()."; + return results; + } + + SqliteCreateViewPtr createView = query.dynamicCast(); + if (!createView) + { + qDebug() << "Parsed object not a CREATE VIEW as expected, but instead it's:" << sqliteQueryTypeToString(query->queryType); + return results; + } + + results = resolveSubSelect(createView->select); + applySubSelectAlias(results, (!alias.isNull() ? alias : name)); + + return results; +} + +bool SelectResolver::isView(const QString& database, const QString& name) +{ + return schemaResolver->getViews(database).contains(name, Qt::CaseInsensitive); +} + +QStringList SelectResolver::getTableColumns(const QString &database, const QString &table, const QString& alias) +{ + Table dbTable; + dbTable.database = database; + dbTable.table = table; + dbTable.alias = alias; + + if (tableColumnsCache.contains(dbTable)) + return tableColumnsCache.value(dbTable); + else + { + QStringList columns = schemaResolver->getTableColumns(database, table); + tableColumnsCache[dbTable] = columns; + return columns; + } +} + +void SelectResolver::applySubSelectAlias(QList& columns, const QString& alias) +{ + // If this subselect is aliased, then all source columns should be considered as from aliased table + QMutableListIterator it(columns); + if (!alias.isNull()) + { + while (it.hasNext()) + { + it.next().tableAlias = alias; + it.value().flags &= ~FROM_ANONYMOUS_SELECT; // remove anonymous flag + } + } + else + { + // Otherwise, mark column as being from anonymous subselect. + // This is used by QueryExecutorColumns step to avoid prefixing result column with table + // when it comes from anonymous subselect (which SQLite needs this to be not prefixed column). + while (it.hasNext()) + it.next().flags |= FROM_ANONYMOUS_SELECT; + } +} + +QString SelectResolver::resolveDatabase(const QString& database) +{ + if (dbNameToAttach.containsRight(database, Qt::CaseInsensitive)) + return dbNameToAttach.valueByRight(database, Qt::CaseInsensitive); + + return database; +} + +int SelectResolver::Table::operator ==(const SelectResolver::Table &other) +{ + return table == other.table && database == other.database && alias == other.alias; +} + +int operator==(const SelectResolver::Table& t1, const SelectResolver::Table& t2) +{ + return t1.table == t2.table && t1.database == t2.database && t1.alias == t2.alias; +} + +uint qHash(const SelectResolver::Table& table) +{ + return qHash(table.database + "." + table.table + "." + table.alias); +} + +int SelectResolver::Column::operator ==(const SelectResolver::Column &other) +{ + return table == other.table && database == other.database && column == other.column && tableAlias == other.tableAlias; +} + +SelectResolver::Table SelectResolver::Column::getTable() +{ + Table resTable; + resTable.table = table; + resTable.database = database; + resTable.originalDatabase = originalDatabase; + resTable.alias = tableAlias; + resTable.flags = flags; + return resTable; +} + +int operator ==(const SelectResolver::Column &c1, const SelectResolver::Column &c2) +{ + return c1.table == c2.table && c1.database == c2.database && c1.column == c2.column && c1.tableAlias == c2.tableAlias; +} + + +uint qHash(const SelectResolver::Column &column) +{ + return qHash(column.database + "." + column.table + "." + column.column + "/" + column.tableAlias); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/selectresolver.h b/SQLiteStudio3/coreSQLiteStudio/selectresolver.h new file mode 100644 index 0000000..c634c5c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/selectresolver.h @@ -0,0 +1,299 @@ +#ifndef SELECTRESOLVER_H +#define SELECTRESOLVER_H + +#include "parser/ast/sqliteselect.h" +#include "common/bistrhash.h" +#include "dialect.h" +#include "expectedtoken.h" +#include +#include +#include + +class Db; +class SchemaResolver; + +/** + * @brief Result column introspection tool + * The SelectResolver provides full information about what would + * result columns be for given SELECT statement. It investigates + * deeply "FROM" clause and the typed result column list + * and in produces list of column objects where each of them + * is described by it's source table, column name in that table, + * a database and a column alias (if any). + * + * The database is a name database as seen by SQLite, which is + * for example "main", "temp", or any name used with "ATTACH". + * + * If the column is not related to the table, but instead is + * an expression, then it's type is set to "OTHER". + * + * If column's table is named with an alias, then it is also provided. + * + * The displayName field describes how would the column be named + * by SQLite itself if it was returned in query results. + * + * The returned column object has also a reference to original + * SqliteSelect::Core::ResultColumn object, so that one can relate + * which queried column produced given column object in this class. + * + * Result column like "table.*" will produce one or more column + * objects from this class. + * + * There's one unsupported case: When the select has a subselect + * in "FROM" clause and that subselect is actually a multi-core + * select (with UNIONs), then columns produced from such source + * won't be related to any table, because currently it's impossible + * for SelectResolver to tell from which table of multi-core + * subselect the column is read from. Therefore in this case + * the column object has it's name, but no table or database. + */ +class API_EXPORT SelectResolver +{ + public: + enum Flag + { + FROM_COMPOUND_SELECT = 0x01, + FROM_ANONYMOUS_SELECT = 0x02, + FROM_DISTINCT_SELECT = 0x04, + FROM_GROUPED_SELECT = 0x08, + FROM_CTE_SELECT = 0x10 + }; + + /** + * @brief Table resolved by the resolver. + */ + struct API_EXPORT Table + { + /** + * @brief Database name. + * + * Either sqlite name, like "main", or "temp", or an attach name. + */ + QString database; + QString originalDatabase; + QString table; + QString alias; + int flags = 0; + + int operator==(const Table& other); + }; + + /** + * @brief Result column resolved by the resolver. + */ + struct API_EXPORT Column + { + enum Type + { + COLUMN, + OTHER + }; + + Type type; + + /** + * @brief Database name. + * + * Either sqlite name, like "main", or "temp", or an attach name. + */ + QString database; + QString originalDatabase; + QString table; + + /** + * @brief Column name or expression. + * + * If a column is of OTHER type, then column member contains detokenized column expression. + */ + QString column; + QString alias; + QString tableAlias; + QString displayName; + int flags = 0; + SqliteSelect::Core::ResultColumn* originalColumn = nullptr; + + int operator==(const Column& other); + Table getTable(); + }; + + SelectResolver(Db* db, const QString &originalQuery); + SelectResolver(Db* db, const QString &originalQuery, const BiStrHash& dbNameToAttach); + ~SelectResolver(); + + QList resolve(SqliteSelect::Core* selectCore); + QList > resolve(SqliteSelect* select); + + QList resolveAvailableColumns(SqliteSelect::Core* selectCore); + QList > resolveAvailableColumns(SqliteSelect* select); + + QSet
resolveTables(SqliteSelect::Core* selectCore); + QList > resolveTables(SqliteSelect* select); + + /** + * @brief Translates tokens representing column name in the SELECT into full column objects. + * @param select Select statement containing all queried tokens. + * @param columnTokens Column tokens to translate. + * @return Full column objects with table and database fields filled in, unless translation failed for some column. + * This method lets you to get full information about columns being specified anywhere in the select, + * no matter if they were typed with database and table prefix or not. It uses smart algorighms + * from the very same class to resolve all available columns, given all data sources of the select, + * finds the queried column token in those available columns and creates full column object. + * Every column token passed to the method will result in a column objects in the results. + * If for some reason the translation was not possible, then the respective column in the results will + * have an OTHER type, while successfully translated columns will have a COLUMN type. + * + * In other words, given the column names (as tokens), this methods finds an occurance of this token in + * the given select statement and provides information in what context was the column used (database, table). + * + * This method is used for example in TableModifier to find out what columns from the modified table + * were used in the referencing CREATE VIEW statements. + */ + QList translateToColumns(SqliteSelect* select, const TokenList& columnTokens); + + /** + * @brief Translates token representing column name in the SELECT into full column objects. + * @param select Select statement containing queried token. + * @param token Column token to translate. + * @return Full column object with table and database fields filled in. + * This method does pretty much the same thing as #translateToColumns(SqliteSelect*,const TokenList&), + * except it takes only one token as an argument. + * + * Internally this method is used by #translateToColumns(SqliteSelect*,const TokenList&) in a loop. + */ + Column translateToColumns(SqliteSelect* select, TokenPtr token); + + /** + * @brief Tells whether there were any errors during resolving. + * @return true if there were any errors, or false otherwise. + */ + bool hasErrors() const; + + /** + * @brief Provides list of errors encountered during resolving. + * @return List of localized error messages. + */ + const QStringList& getErrors() const; + + /** + * @brief resolveMultiCore + * If true (by default), the multi-core subselects will be resolved using + * first core from the list. If false, then the subselect will be ignored by resolver, + * which will result in empty columns and/or tables resolved for that subselect. + */ + bool resolveMultiCore = true; + + /** + * @brief ignoreInvalidNames + * If true, then problems with resolving real objects in sqlite_master mentioned in the select, + * are ignored silently. Otherwise those accidents are reported with qDebug(). + */ + bool ignoreInvalidNames = false; + + private: + QList resolveCore(SqliteSelect::Core* selectCore); + QList resolveAvailableCoreColumns(SqliteSelect::Core* selectCore); + Column translateTokenToColumn(SqliteSelect* select, TokenPtr token); + void resolve(SqliteSelect::Core::ResultColumn* resCol); + void resolveStar(SqliteSelect::Core::ResultColumn* resCol); + void resolveExpr(SqliteSelect::Core::ResultColumn* resCol); + void resolveDbAndTable(SqliteSelect::Core::ResultColumn *resCol); + Column resolveRowIdColumn(SqliteExpr* expr); + Column resolveExplicitColumn(const QString& columnName); + Column resolveExplicitColumn(const QString& table, const QString& columnName); + Column resolveExplicitColumn(const QString& database, const QString& table, const QString& columnName); + + QList resolveJoinSource(SqliteSelect::Core::JoinSource* joinSrc); + QList resolveSingleSource(SqliteSelect::Core::SingleSource* joinSrc); + QList resolveSingleSourceSubSelect(SqliteSelect::Core::SingleSource* joinSrc); + QList resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc); + QList resolveSubSelect(SqliteSelect* select); + QList resolveView(const QString& database, const QString& name, const QString &alias); + bool isView(const QString& database, const QString& name); + QStringList getTableColumns(const QString& database, const QString& table, const QString &alias); + void applySubSelectAlias(QList& columns, const QString& alias); + QString resolveDatabase(const QString& database); + + void markDistinctColumns(); + void markCompoundColumns(); + void markCteColumns(); + void markGroupedColumns(); + void fixColumnNames(); + void markCurrentColumnsWithFlag(Flag flag); + bool matchTable(const Column& sourceColumn, const QString& table); + TokenList getResColTokensWithoutAlias(SqliteSelect::Core::ResultColumn *resCol); + + Db* db = nullptr; + QString query; + + /** + * @brief Database name to attach name map. + * + * When this map is defined, then every occurance of the database in the query will be + * checked against being an attach name and if it is an attach name, then it will be + * translated into the original database name used in the query using this map. + * + * This will result in original database names in "originalDatabase" and "displayName" + * members returned from the resolver. + * + * The map should be collected when the attaching is performed. For example DbAttacher + * does the job for you - it attaches databases and prepares the map, that you can + * use here. + */ + BiStrHash dbNameToAttach; + + /** + * @brief currentCoreResults + * List of columns that will be returned from resultColumns of the queried SELECT. + * Columns are linked to their tables from "FROM" clause if possible, + * otherwise they have null table name. + * This list is built progressively when iterating through result columns. + * Then it's returned from resolve() call. + */ + QList currentCoreResults; + + /** + * @brief tableColumnsCache + * When the resolver asks database for list of its columns (by PRAGMA table_info()), + * then it stores results in this cache, becuase it's very likely that this table + * will be queried for columns more times. + */ + QHash tableColumnsCache; + + /** + * @brief currentCoreSourceColumns + * List of every column available from current selectCore sources. + * There can be many columns with same database and table. + * It can be also interpreted as list of all available tables in the "FROM" clause + * (but only at the top level, recursive subselects or subjoins). + * This list is created at the begining of resolve() call, before any result column + * is being actually resolved. This is also created by resolveTables() call + * and in that case no result columns are being resolved, just this list is being + * converted into the list of SqliteStatement::Table and returned. + * + * Note, that some entries in this list will have only column name filled in + * and no table related information - this happens when the column comes from + * subselect, where it was not a table related result column. + */ + QList currentCoreSourceColumns; + + /** + * @brief schemaResolver + * Used to get list of column names in a table. + */ + SchemaResolver* schemaResolver = nullptr; + + /** + * @brief List of errors encountered during resolving. + * + * This may contain errors like missing table alias that was used in result column list, etc. + */ + QStringList errors; +}; + +API_EXPORT int operator==(const SelectResolver::Table& t1, const SelectResolver::Table& t2); +API_EXPORT uint qHash(const SelectResolver::Table& table); + +API_EXPORT int operator==(const SelectResolver::Column& c1, const SelectResolver::Column& c2); +API_EXPORT uint qHash(const SelectResolver::Column& column); + +#endif // SELECTRESOLVER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp new file mode 100644 index 0000000..54b0905 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp @@ -0,0 +1,202 @@ +#include "bugreporter.h" +#include "services/config.h" +#include "services/notifymanager.h" +#include +#include +#include +#include + +BugReporter::BugReporter(QObject *parent) : + QObject(parent) +{ + networkManager = new QNetworkAccessManager(this); + connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*))); +} + +QUrl BugReporter::getReporterEmailHelpUrl() const +{ + return QUrl(QString::fromLatin1(reporterEmailHelpUrl)); +} + +QUrl BugReporter::getReporterUserAndPasswordHelpUrl() const +{ + return QUrl(QString::fromLatin1(reporterUserPassHelpUrl)); +} + +void BugReporter::validateBugReportCredentials(const QString& login, const QString& password) +{ + if (credentialsValidationInProgress) + { + credentialsValidationInProgress->abort(); + credentialsValidationInProgress->deleteLater(); + } + + QUrlQuery query; + query.addQueryItem("validateUser", login); + query.addQueryItem("password", password); + + QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + query.query(QUrl::FullyEncoded)); + QNetworkRequest request(url); + credentialsValidationInProgress = networkManager->get(request); + replyToHandler[credentialsValidationInProgress] = [this](bool success, const QString& data) + { + if (success && data.trimmed() != "OK") + { + success = false; + emit credentialsValidationResult(success, tr("Invalid login or password")); + } + else + { + emit credentialsValidationResult(success, success ? QString() : data); + } + }; +} + +void BugReporter::abortCredentialsValidation() +{ + if (credentialsValidationInProgress) + { + credentialsValidationInProgress->abort(); + credentialsValidationInProgress->deleteLater(); + credentialsValidationInProgress = nullptr; + } +} + +void BugReporter::useBugReportCredentials(const QString& login, const QString& password) +{ + CFG_CORE.Internal.BugReportUser.set(login); + CFG_CORE.Internal.BugReportPassword.set(password); +} + +void BugReporter::clearBugReportCredentials() +{ + CFG_CORE.Internal.BugReportUser.set(QString()); + CFG_CORE.Internal.BugReportPassword.set(QString()); +} + +void BugReporter::reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, BugReporter::ResponseHandler responseHandler, const QString& urlSuffix) +{ + static_qstring(contentsTpl, "%1\n\nPlugins loaded:\n%2\n\nVersion:\n%3\n\nOperating System:\n%4"); + QString contents = contentsTpl.arg(escapeParam(details), plugins, version, os); + + QUrlQuery query; + query.addQueryItem("brief", escapeParam(title)); + query.addQueryItem("contents", contents); + query.addQueryItem("os", os); + query.addQueryItem("version", version); + query.addQueryItem("featureRequest", "0"); + + QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + escapeUrl(query.query(QUrl::FullyEncoded) + urlSuffix)); + QNetworkRequest request(url); + QNetworkReply* reply = networkManager->get(request); + if (responseHandler) + replyToHandler[reply] = responseHandler; + + replyToTypeAndTitle[reply] = QPair(false, title); +} + +void BugReporter::requestFeature(const QString& title, const QString& details, BugReporter::ResponseHandler responseHandler, const QString& urlSuffix) +{ + QUrlQuery query; + query.addQueryItem("brief", escapeParam(title)); + query.addQueryItem("contents", escapeParam(details)); + query.addQueryItem("featureRequest", "1"); + + QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + escapeUrl(query.query(QUrl::FullyEncoded) + urlSuffix)); + QNetworkRequest request(url); + QNetworkReply* reply = networkManager->get(request); + if (responseHandler) + replyToHandler[reply] = responseHandler; + + replyToTypeAndTitle[reply] = QPair(true, title); +} + +QString BugReporter::escapeParam(const QString &input) +{ + return input.toHtmlEscaped(); +} + +QString BugReporter::escapeUrl(const QString &input) +{ + // For some reason the ";" character is not encodedy by QUrlQuery when using FullEncoded. Pity. We have to do it manually. + QString copy = input; + return copy.replace(";", "%3B"); +} + +void BugReporter::finished(QNetworkReply* reply) +{ + if (reply == credentialsValidationInProgress) + credentialsValidationInProgress = nullptr; + + if (!replyToHandler.contains(reply)) + { + reply->deleteLater(); + return; + } + + bool success = (reply->error() == QNetworkReply::NoError); + QString data; + if (success) + data = QString::fromLatin1(reply->readAll()); + else + data = reply->errorString(); + + replyToHandler[reply](success, data); + replyToHandler.remove(reply); + + if (replyToTypeAndTitle.contains(reply)) + { + if (success) + CFG->addReportHistory(replyToTypeAndTitle[reply].first, replyToTypeAndTitle[reply].second, data); + + replyToTypeAndTitle.remove(reply); + } + + reply->deleteLater(); +} + +void BugReporter::reportBug(const QString& email, const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, + ResponseHandler responseHandler) +{ + QUrlQuery query; + query.addQueryItem("byEmail", email); + QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); + + reportBug(title, details, version, os, plugins, responseHandler, urlSuffix); +} + +void BugReporter::reportBug(const QString& title, const QString& details, const QString& version, const QString& os, + const QString& plugins, ResponseHandler responseHandler) +{ + QString user = CFG_CORE.Internal.BugReportUser.get(); + QString pass = CFG_CORE.Internal.BugReportPassword.get(); + + QUrlQuery query; + query.addQueryItem("byUser", user); + query.addQueryItem("password", pass); + QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); + + reportBug(title, details, version, os, plugins, responseHandler, urlSuffix); +} + +void BugReporter::requestFeature(const QString& email, const QString& title, const QString& details, ResponseHandler responseHandler) +{ + QUrlQuery query; + query.addQueryItem("byEmail", email); + QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); + + requestFeature(title, details, responseHandler, urlSuffix); +} + +void BugReporter::requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler) +{ + QString user = CFG_CORE.Internal.BugReportUser.get(); + QString pass = CFG_CORE.Internal.BugReportPassword.get(); + + QUrlQuery query; + query.addQueryItem("byUser", user); + query.addQueryItem("password", pass); + QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); + + requestFeature(title, details, responseHandler, urlSuffix); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h new file mode 100644 index 0000000..3e8eb8d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h @@ -0,0 +1,62 @@ +#ifndef BUGREPORTER_H +#define BUGREPORTER_H + +#include "common/global.h" +#include "sqlitestudio.h" +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; + +class API_EXPORT BugReporter : public QObject +{ + Q_OBJECT + + public: + typedef std::function ResponseHandler; + + explicit BugReporter(QObject *parent = 0); + + QUrl getReporterEmailHelpUrl() const; + QUrl getReporterUserAndPasswordHelpUrl() const; + void validateBugReportCredentials(const QString& login, const QString& password); + void abortCredentialsValidation(); + void useBugReportCredentials(const QString& login, const QString& password); + void clearBugReportCredentials(); + + private: + void reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, + ResponseHandler responseHandler, const QString& urlSuffix); + void requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler, const QString& urlSuffix); + + static QString escapeParam(const QString& input); + static QString escapeUrl(const QString& input); + + QNetworkAccessManager* networkManager = nullptr; + QHash replyToHandler; + QHash> replyToTypeAndTitle; + QNetworkReply* credentialsValidationInProgress = nullptr; + + static_char* bugReportServiceUrl = "http://sqlitestudio.pl/report_bug3.rvt"; + static_char* reporterEmailHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Reporter_email_address"; + static_char* reporterUserPassHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Reporter_user_and_password"; + + signals: + void credentialsValidationResult(bool success, const QString& errorMessage); + + private slots: + void finished(QNetworkReply* reply); + + public slots: + void reportBug(const QString& email, const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, + ResponseHandler responseHandler = nullptr); + void reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, + ResponseHandler responseHandler = nullptr); + void requestFeature(const QString& email, const QString& title, const QString& details, ResponseHandler responseHandler = nullptr); + void requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler = nullptr); +}; + +#define BUGS SQLITESTUDIO->getBugReporter() + +#endif // BUGREPORTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.cpp b/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.cpp new file mode 100644 index 0000000..e02508f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.cpp @@ -0,0 +1,91 @@ +#include "codeformatter.h" +#include "parser/parser.h" +#include "plugins/codeformatterplugin.h" +#include "services/pluginmanager.h" +#include + +void CodeFormatter::setFormatter(const QString& lang, CodeFormatterPlugin *formatterPlugin) +{ + currentFormatter[lang] = formatterPlugin; +} + +CodeFormatterPlugin* CodeFormatter::getFormatter(const QString& lang) +{ + if (hasFormatter(lang)) + return currentFormatter[lang]; + + return nullptr; +} + +bool CodeFormatter::hasFormatter(const QString& lang) +{ + return currentFormatter.contains(lang); +} + +void CodeFormatter::fullUpdate() +{ + availableFormatters.clear(); + QList formatterPlugins = PLUGINS->getLoadedPlugins(); + for (CodeFormatterPlugin* plugin : formatterPlugins) + availableFormatters[plugin->getLanguage()][plugin->getName()] = plugin; + + updateCurrent(); +} + +void CodeFormatter::updateCurrent() +{ + if (modifyingConfig) + return; + + modifyingConfig = true; + + bool modified = false; + currentFormatter.clear(); + QHash config = CFG_CORE.General.ActiveCodeFormatter.get(); + QString name; + QStringList names = availableFormatters.keys(); + qSort(names); + for (const QString& lang : names) + { + name = config[lang].toString(); + if (config.contains(lang) && availableFormatters[lang].contains(name)) + { + currentFormatter[lang] = availableFormatters[lang][name]; + } + else + { + currentFormatter[lang] = availableFormatters[lang].begin().value(); + config[lang] = currentFormatter[lang]->getName(); + modified = true; + } + } + + if (modified) + CFG_CORE.General.ActiveCodeFormatter.set(config); + + modifyingConfig = false; +} + +void CodeFormatter::storeCurrentSettings() +{ + QHash config = CFG_CORE.General.ActiveCodeFormatter.get(); + QHashIterator it(currentFormatter); + while (it.hasNext()) + { + it.next(); + config[it.key()] = it.value()->getName(); + } + + CFG_CORE.General.ActiveCodeFormatter.set(config); +} + +QString CodeFormatter::format(const QString& lang, const QString& code, Db* contextDb) +{ + if (!hasFormatter(lang)) + { + qWarning() << "No formatter plugin defined for CodeFormatter for language:" << lang; + return code; + } + + return currentFormatter[lang]->format(code, contextDb); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.h b/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.h new file mode 100644 index 0000000..015bbfe --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.h @@ -0,0 +1,30 @@ +#ifndef CODEFORMATTER_H +#define CODEFORMATTER_H + +#include "coreSQLiteStudio_global.h" +#include "sqlitestudio.h" + +class CodeFormatterPlugin; +class Db; + +class API_EXPORT CodeFormatter +{ + public: + QString format(const QString& lang, const QString& code, Db* contextDb); + + void setFormatter(const QString& lang, CodeFormatterPlugin* formatterPlugin); + CodeFormatterPlugin* getFormatter(const QString& lang); + bool hasFormatter(const QString& lang); + void fullUpdate(); + void updateCurrent(); + void storeCurrentSettings(); + + private: + QHash> availableFormatters; + QHash currentFormatter; + bool modifyingConfig = false; +}; + +#define FORMATTER SQLITESTUDIO->getCodeFormatter() + +#endif // CODEFORMATTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/collationmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/collationmanager.h new file mode 100644 index 0000000..5a05b0f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/collationmanager.h @@ -0,0 +1,41 @@ +#ifndef COLLATIONMANAGER_H +#define COLLATIONMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include +#include +#include +#include + +class Db; + +class API_EXPORT CollationManager : public QObject +{ + Q_OBJECT + + public: + struct API_EXPORT Collation + { + QString name; + QString lang; + QString code; + QStringList databases; + bool allDatabases = true; + }; + + typedef QSharedPointer CollationPtr; + + virtual void setCollations(const QList& newCollations) = 0; + virtual QList getAllCollations() const = 0; + virtual QList getCollationsForDatabase(const QString& dbName) const = 0; + virtual int evaluate(const QString& name, const QString& value1, const QString& value2) = 0; + virtual int evaluateDefault(const QString& value1, const QString& value2) = 0; + + signals: + void collationListChanged(); +}; + +#define COLLATIONS SQLITESTUDIO->getCollationManager() + +#endif // COLLATIONMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/config.cpp b/SQLiteStudio3/coreSQLiteStudio/services/config.cpp new file mode 100644 index 0000000..1fef317 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/config.cpp @@ -0,0 +1,9 @@ +#include "services/config.h" + +CFG_DEFINE(Core) + +static const QString DB_FILE_NAME = QStringLiteral("settings3"); + +Config::~Config() +{ +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/config.h b/SQLiteStudio3/coreSQLiteStudio/services/config.h new file mode 100644 index 0000000..5a3f594 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/config.h @@ -0,0 +1,178 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "coreSQLiteStudio_global.h" +#include "config_builder.h" +#include "services/functionmanager.h" +#include "collationmanager.h" +#include "sqlitestudio.h" +#include "common/utils.h" +#include +#include +#include +#include +#include +#include + +const int SQLITESTUDIO_CONFIG_VERSION = 1; + +CFG_CATEGORIES(Core, + CFG_CATEGORY(General, + CFG_ENTRY(int, SqlHistorySize, 10000) + CFG_ENTRY(int, DdlHistorySize, 1000) + CFG_ENTRY(QString, LoadedPlugins, "") + CFG_ENTRY(QVariantHash, ActiveCodeFormatter, QVariantHash()) + CFG_ENTRY(bool, CheckUpdatesOnStartup, true) + ) + CFG_CATEGORY(Console, + CFG_ENTRY(int, HistorySize, 100) + ) + CFG_CATEGORY(Internal, + CFG_ENTRY(QVariantList, Functions, QVariantList()) + CFG_ENTRY(QVariantList, Collations, QVariantList()) + CFG_ENTRY(QString, BugReportUser, QString()) + CFG_ENTRY(QString, BugReportPassword, QString()) + CFG_ENTRY(QString, BugReportRecentTitle, QString()) + CFG_ENTRY(QString, BugReportRecentContents, QString()) + CFG_ENTRY(bool, BugReportRecentError, false) + ) +) + +#define CFG_CORE CFG_INSTANCE(Core) + +class QAbstractItemModel; +class DdlHistoryModel; + +class API_EXPORT Config : public QObject +{ + Q_OBJECT + + public: + virtual ~Config(); + + struct CfgDb + { + QString name; + QString path; + QHash options; + }; + + typedef QSharedPointer CfgDbPtr; + + struct DbGroup; + typedef QSharedPointer DbGroupPtr; + + struct DbGroup + { + qint64 id; + QString referencedDbName; + QString name; + QList childs; + int order; + bool open = false; + }; + + struct SqlHistoryEntry + { + QString query; + QString dbName; + int rowsAffected; + int unixtime; + }; + + typedef QSharedPointer SqlHistoryEntryPtr; + + struct DdlHistoryEntry + { + QString dbName; + QString dbFile; + QDateTime timestamp; + QString queries; + }; + + typedef QSharedPointer DdlHistoryEntryPtr; + + struct ReportHistoryEntry + { + int id = 0; + bool isFeatureRequest = false; + int timestamp = 0; + QString title; + QString url; + }; + + typedef QSharedPointer ReportHistoryEntryPtr; + + virtual void init() = 0; + virtual void cleanUp() = 0; + virtual const QString& getConfigDir() const = 0; + virtual QString getConfigFilePath() const = 0; + + virtual void beginMassSave() = 0; + virtual void commitMassSave() = 0; + virtual void rollbackMassSave() = 0; + virtual bool isMassSaving() const = 0; + virtual void set(const QString& group, const QString& key, const QVariant& value) = 0; + virtual QVariant get(const QString& group, const QString& key) = 0; + virtual QHash getAll() = 0; + + virtual bool addDb(const QString& name, const QString& path, const QHash &options) = 0; + virtual bool updateDb(const QString& name, const QString &newName, const QString& path, const QHash &options) = 0; + virtual bool removeDb(const QString& name) = 0; + virtual bool isDbInConfig(const QString& name) = 0; + virtual QString getLastErrorString() const = 0; + + /** + * @brief Provides list of all registered databases. + * @return List of database entries. + * + * Registered databases are those that user added to the application. They are not necessary valid or supported. + * They can be inexisting or unsupported, but they are kept in registry in case user fixes file path, + * or loads plugin to support it. + */ + virtual QList dbList() = 0; + virtual CfgDbPtr getDb(const QString& dbName) = 0; + + virtual void storeGroups(const QList& groups) = 0; + virtual QList getGroups() = 0; + virtual DbGroupPtr getDbGroup(const QString& dbName) = 0; + + virtual qint64 addSqlHistory(const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) = 0; + virtual void updateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) = 0; + virtual void clearSqlHistory() = 0; + virtual QAbstractItemModel* getSqlHistoryModel() = 0; + + virtual void addCliHistory(const QString& text) = 0; + virtual void applyCliHistoryLimit() = 0; + virtual void clearCliHistory() = 0; + virtual QStringList getCliHistory() const = 0; + + virtual void addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) = 0; + virtual QList getDdlHistoryFor(const QString& dbName, const QString& dbFile, const QDate& date) = 0; + virtual DdlHistoryModel* getDdlHistoryModel() = 0; + virtual void clearDdlHistory() = 0; + + virtual void addReportHistory(bool isFeatureRequest, const QString& title, const QString& url) = 0; + virtual QList getReportHistory() = 0; + virtual void deleteReport(int id) = 0; + virtual void clearReportHistory() = 0; + + virtual void begin() = 0; + virtual void commit() = 0; + virtual void rollback() = 0; + + signals: + void massSaveBegins(); + void massSaveCommited(); + void sqlHistoryRefreshNeeded(); + void ddlHistoryRefreshNeeded(); + void reportsHistoryRefreshNeeded(); + + public slots: + virtual void refreshSqlHistory() = 0; + virtual void refreshDdlHistory() = 0; +}; + +#define CFG SQLITESTUDIO->getConfig() + +#endif // CONFIG_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.cpp new file mode 100644 index 0000000..91aff79 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.cpp @@ -0,0 +1,17 @@ +#include "dbmanager.h" +#include + +DbManager::DbManager(QObject *parent) : + QObject(parent) +{ +} + +DbManager::~DbManager() +{ +} + +QString DbManager::generateDbName(const QString &filePath) +{ + QFileInfo fi(filePath); + return fi.completeBaseName(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h new file mode 100644 index 0000000..5a56151 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h @@ -0,0 +1,288 @@ +#ifndef DBMANAGER_H +#define DBMANAGER_H + +#include "db/db.h" +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include "sqlitestudio.h" +#include +#include +#include + +/** @file */ + +class DbPlugin; +class Config; +class Plugin; +class PluginType; + +/** + * @brief Database registry manager. + * + * Manages list of databases in SQLiteStudio core. + * + * It's a singleton asseccible with DBLIST macro. + */ +class API_EXPORT DbManager : public QObject +{ + Q_OBJECT + + public: + /** + * @brief Creates database manager. + * @param parent Parent object passed to QObject constructor. + */ + explicit DbManager(QObject *parent = 0); + + /** + * @brief Default destructor. + */ + ~DbManager(); + + /** + * @brief Adds database to the manager. + * @param name Symbolic name of the database, as it will be presented in the application. + * @param path Path to the database file. + * @param options Key-value custom options for database, that can be used in the DbPlugin implementation, like connection password, etc. + * @param permanent If true, then the database will be remembered in configuration, otherwise it will be disappear after application restart. + * @return true if the database has been successfully added, or false otherwise. + * + * The method can return false if given database file exists, but is not supported SQLite version (including invalid files, + * that are not SQLite database). It basicly returns false if DbPlugin#getInstance() returned null for given database parameters. + */ + virtual bool addDb(const QString &name, const QString &path, const QHash &options, bool permanent = true) = 0; + + /** + * @overload + */ + virtual bool addDb(const QString &name, const QString &path, bool permanent = true) = 0; + + /** + * @brief Adds database as temporary, with generated name. + * @param path Path to database. + * @param options Key-value custom options for database. + * @return Added database name, if the database has been successfully added, or null string otherwise. + * + * This method is used for example when database was passed as argument to application command line arguments. + */ + virtual QString quickAddDb(const QString &path, const QHash &options) = 0; + + /** + * @brief Updates registered database with new data. + * @param db Registered database. + * @param name New symbolic name for the database. + * @param path New database file path. + * @param options New database options. See addDb() for details. + * @param permanent True to make the database stored in configuration, false to make it disappear after application restart. + * @return true if the database was successfully updated, or false otherwise. + */ + virtual bool updateDb(Db* db, const QString &name, const QString &path, const QHash &options, bool permanent) = 0; + + /** + * @brief Removes database from application. + * @param name Symbolic name of the database. + * @param cs Should the name be compare with case sensitivity? + */ + virtual void removeDbByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive) = 0; + + /** + * @brief Removes database from application. + * @param path Database file path as it was passed to addDb() or updateDb(). + */ + virtual void removeDbByPath(const QString& path) = 0; + + /** + * @brief Removes database from application. + * @param db Database to be removed. + */ + virtual void removeDb(Db* db) = 0; + + /** + * @brief Gives list of databases registered in the application. + * @return List of databases, no matter if database is open or not. + * + * The results list includes invalid databases (not supported by driver plugin, or with no read access, etc). + */ + virtual QList getDbList() = 0; + + /** + * @brief Gives list of valid databases. + * @return List of open databases. + */ + virtual QList getValidDbList() = 0; + + /** + * @brief Gives list of currently open databases. + * @return List of open databases. + */ + virtual QList getConnectedDbList() = 0; + + /** + * @brief Gives list of database names. + * @return List of database names that are registered in the application. + */ + virtual QStringList getDbNames() = 0; + + /** + * @brief Gives database object by its name. + * @param name Symbolic name of the database. + * @param cs Should the \p name be compared with case sensitivity? + * @return Database object, or null pointer if the database could not be found. + * + * This method is fast, as it uses hash table lookup. + */ + virtual Db* getByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive) = 0; + + /** + * @brief Gives database object by its file path. + * @param path Database file path as it was passed to addDb() or updateDb(). + * @return Database matched by file path, or null if no database was found. + * + * This method is fast, as it uses hash table lookup. + */ + virtual Db* getByPath(const QString& path) = 0; + + /** + * @brief Creates in-memory SQLite3 database. + * @return Created database. + * + * Created database can be used for any purpose. Note that DbManager doesn't own created + * database and it's up to the caller to delete the database when it's no longer needed. + */ + virtual Db* createInMemDb() = 0; + + /** + * @brief Tells if given database is temporary. + * @param db Database to check. + * @return true if database is temporary, or false if it's stored in the configuration. + * + * Temporary databases are databases that are not stored in configuration and will not be restored + * upon next SQLiteStudio start. This can be decided by user on UI when he edits database registration info + * (there is a checkbox for that). + */ + virtual bool isTemporary(Db* db) = 0; + + /** + * @brief Generates database name. + * @param filePath Database file path. + * @return A name, using database file name as a hint for a name. + * + * This method doesn't care about uniqueness of the name. It just gets the file name from provided path + * and uses it as a name. + */ + static QString generateDbName(const QString& filePath); + + public slots: + /** + * @brief Rescans configuration for new database entries. + * + * Looks into the configuration for new databases. If there are any, adds them to list of managed databases. + */ + virtual void scanForNewDatabasesInConfig() = 0; + + /** + * @brief Sends signal to all interested entities, that databases are loaded. + * + * This is called by the managing entity (the SQLiteStudio instance) to let all know, + * that all db-related plugins and configuration related to databases are now loaded + * and list of databases in the manager is complete. + */ + virtual void notifyDatabasesAreLoaded() = 0; + + signals: + /** + * @brief Application just connected to the database. + * @param db Database object that the connection was made to. + * + * Emitted just after application has connected to the database. + */ + void dbConnected(Db* db); + + /** + * @brief Application just disconnected from the database. + * @param db Database object that the connection was closed with. + */ + void dbDisconnected(Db* db); + + /** + * @brief The database is about to be disconnected and user can still deny it. + * @param db Database to be closed. + * @param deny If set to true, then disconnecting will be aborted. + */ + void dbAboutToBeDisconnected(Db* db, bool& deny); + + /** + * @brief A database has been added to the application. + * @param db Database added. + * Emitted from addDb() methods in case of success. + */ + void dbAdded(Db* db); + + /** + * @brief A database has been removed from the application. + * @param db Database object that was removed. The object still exists, but will be removed soon after this signal is handled. + * + * Emitted from removeDb(). As the argument is a smart pointer, the object will be deleted after last reference to the pointer + * is deleted, which is very likely that the pointer instance in this signal is the last one. + */ + void dbRemoved(Db* db); + + /** + * @brief A database registration data has been updated. + * @param oldName The name of the database before the update - in case the name was updated. + * @param db Database object that was updated. + * + * Emitted from updateDb() after successful update. + * + * The name of the database is a key for tables related to the databases, so if it changed, we dbUpdated() provides + * the original name before update, so any tables can be updated basing on the old name. + */ + void dbUpdated(const QString& oldName, Db* db); + + /** + * @brief Loaded plugin to support the database. + * @param db Database object handled by the plugin. + * + * Emitted after a plugin was loaded and it turned out to handle the database that was already registered in the application, + * but wasn't managed by database manager, because no handler plugin was loaded earlier. + * + * Also emitted when database details were edited and saved, which fixes database configuration (for example path). + */ + void dbLoaded(Db* db); + + /** + * @brief Plugin supporting the database is about to be unloaded. + * @param db Database object to be removed from the manager. + * @param plugin Plugin that handles the database. + * + * Emitted when PluginManager is about to unload the plugin which is handling the database. + * All classes using this database object should stop using it immediately, or the application may crash. + * + * The plugin itself should not use this signal. Instead it should implement Plugin::deinit() method + * to perform deinitialization before unloading. The Plugin::deinit() method is called before this signal is emitted. + */ + void dbAboutToBeUnloaded(Db* db, DbPlugin* plugin); + + /** + * @brief Plugins supporting the database was just unloaded. + * @param db The new database object (InvalidDb) that replaced the previous one. + * + * This is emitted after the plugin for the database was unloaded. The \p db object is now a different object. + * It is of InvalidDb class and it represents a database in an invalid state. It still has name, path and connection options, + * but no operation can be performed on the database. + */ + void dbUnloaded(Db* db); + + /** + * @brief Emited when the initial database list has been loaded. + */ + void dbListLoaded(); +}; + +/** + * @brief Database manager. + * Provides direct access to the database manager. + */ +#define DBLIST SQLITESTUDIO->getDbManager() + +#endif // DBMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.cpp new file mode 100644 index 0000000..6f916bc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.cpp @@ -0,0 +1,283 @@ +#include "exportmanager.h" +#include "services/pluginmanager.h" +#include "plugins/exportplugin.h" +#include "services/notifymanager.h" +#include "db/queryexecutor.h" +#include "exportworker.h" +#include +#include +#include +#include +#include +#include + +ExportManager::ExportManager(QObject *parent) : + PluginServiceBase(parent) +{ +} + +ExportManager::~ExportManager() +{ + safe_delete(config); +} + +QStringList ExportManager::getAvailableFormats(ExportMode exportMode) const +{ + QStringList formats; + for (ExportPlugin* plugin : PLUGINS->getLoadedPlugins()) + { + if (exportMode == UNDEFINED || plugin->getSupportedModes().testFlag(exportMode)) + formats << plugin->getFormatName(); + } + + return formats; +} + +void ExportManager::configure(const QString& format, const StandardExportConfig& config) +{ + configure(format, new StandardExportConfig(config)); +} + +void ExportManager::configure(const QString& format, StandardExportConfig* config) +{ + if (exportInProgress) + { + qWarning() << "Tried to configure export while another export is in progress."; + return; + } + + plugin = getPluginForFormat(format); + if (!plugin) + { + invalidFormat(format); + return; + } + + safe_delete(this->config); + this->config = config; +} + +bool ExportManager::isExportInProgress() const +{ + return exportInProgress; +} + +void ExportManager::exportQueryResults(Db* db, const QString& query) +{ + if (!checkInitialConditions()) + return; + + if (!plugin->getSupportedModes().testFlag(QUERY_RESULTS)) + { + notifyError(tr("Export plugin %1 doesn't support exporing query results.").arg(plugin->getFormatName())); + emit exportFailed(); + emit exportFinished(); + return; + } + + exportInProgress = true; + mode = QUERY_RESULTS; + + ExportWorker* worker = prepareExport(); + if (!worker) + return; + + worker->prepareExportQueryResults(db, query); + QThreadPool::globalInstance()->start(worker); +} + +void ExportManager::exportTable(Db* db, const QString& database, const QString& table) +{ + static const QString sql = QStringLiteral("SELECT * FROM %1"); + + if (!checkInitialConditions()) + return; + + if (!plugin->getSupportedModes().testFlag(TABLE)) + { + notifyError(tr("Export plugin %1 doesn't support exporing tables.").arg(plugin->getFormatName())); + emit exportFailed(); + emit exportFinished(); + return; + } + + exportInProgress = true; + mode = TABLE; + + ExportWorker* worker = prepareExport(); + if (!worker) + return; + + worker->prepareExportTable(db, database, table); + QThreadPool::globalInstance()->start(worker); +} + +void ExportManager::exportDatabase(Db* db, const QStringList& objectListToExport) +{ + if (!checkInitialConditions()) + return; + + if (!plugin->getSupportedModes().testFlag(DATABASE)) + { + notifyError(tr("Export plugin %1 doesn't support exporing databases.").arg(plugin->getFormatName())); + emit exportFailed(); + emit exportFinished(); + return; + } + + exportInProgress = true; + mode = DATABASE; + + ExportWorker* worker = prepareExport(); + if (!worker) + return; + + worker->prepareExportDatabase(db, objectListToExport); + QThreadPool::globalInstance()->start(worker); +} + +void ExportManager::interrupt() +{ + emit orderWorkerToInterrupt(); +} + +ExportPlugin* ExportManager::getPluginForFormat(const QString& formatName) const +{ + for (ExportPlugin* plugin : PLUGINS->getLoadedPlugins()) + if (plugin->getFormatName() == formatName) + return plugin; + + return nullptr; +} + +void ExportManager::invalidFormat(const QString& format) +{ + notifyError(tr("Export format '%1' is not supported. Supported formats are: %2.").arg(format).arg(getAvailableFormats().join(", "))); +} + +bool ExportManager::checkInitialConditions() +{ + if (exportInProgress) + { + qWarning() << "Tried to call export while another export is in progress."; + emit exportFailed(); + emit exportFinished(); + return false; + } + + if (!plugin) + { + qWarning() << "Tried to call export while no export plugin was configured."; + emit exportFailed(); + emit exportFinished(); + return false; + } + + return true; +} + +ExportWorker* ExportManager::prepareExport() +{ + bool usesOutput = plugin->getSupportedModes().testFlag(FILE) || plugin->getSupportedModes().testFlag(CLIPBOARD); + QIODevice* output = nullptr; + if (usesOutput) + { + output = getOutputStream(); + if (!output) + { + emit exportFailed(); + emit exportFinished(); + exportInProgress = false; + return nullptr; + } + } + + ExportWorker* worker = new ExportWorker(plugin, config, output); + connect(worker, SIGNAL(finished(bool,QIODevice*)), this, SLOT(finalizeExport(bool,QIODevice*))); + connect(this, SIGNAL(orderWorkerToInterrupt()), worker, SLOT(interrupt())); + return worker; +} + +void ExportManager::handleClipboardExport() +{ + if (plugin->getMimeType().isNull()) + { + QString str = codecForName(config->codec)->toUnicode(bufferForClipboard->buffer()); + emit storeInClipboard(str); + } + else + emit storeInClipboard(bufferForClipboard->buffer(), plugin->getMimeType()); +} + +void ExportManager::finalizeExport(bool result, QIODevice* output) +{ + if (result) + { + if (config->intoClipboard) + { + notifyInfo(tr("Export to the clipboard was successful.")); + handleClipboardExport(); + } + else if (!config->outputFileName.isEmpty()) + notifyInfo(tr("Export to the file '%1' was successful.").arg(config->outputFileName)); + else + notifyInfo(tr("Export to was successful.").arg(config->outputFileName)); + + emit exportSuccessful(); + } + else + { + emit exportFailed(); + } + emit exportFinished(); + + if (output) + { + output->close(); + delete output; + } + + bufferForClipboard = nullptr; + exportInProgress = false; +} + +QIODevice* ExportManager::getOutputStream() +{ + QFile::OpenMode openMode; + if (config->intoClipboard) + { + openMode = QIODevice::WriteOnly; + if (!plugin->isBinaryData()) + openMode |= QIODevice::Text; + + bufferForClipboard = new QBuffer(); + bufferForClipboard->open(openMode); + return bufferForClipboard; + } + else if (!config->outputFileName.trimmed().isEmpty()) + { + openMode = QIODevice::WriteOnly|QIODevice::Truncate; + if (!plugin->isBinaryData()) + openMode |= QIODevice::Text; + + QFile* file = new QFile(config->outputFileName); + if (!file->open(openMode)) + { + notifyError(tr("Could not export to file %1. File cannot be open for writting.").arg(config->outputFileName)); + delete file; + return nullptr; + } + return file; + } + else + { + qCritical() << "ExportManager::getOutputStream(): neither clipboard or output file was specified"; + } + + return nullptr; +} + +bool ExportManager::isAnyPluginAvailable() +{ + return !PLUGINS->getLoadedPlugins().isEmpty(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.h new file mode 100644 index 0000000..64d1136 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.h @@ -0,0 +1,234 @@ +#ifndef EXPORTMANAGER_H +#define EXPORTMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "db/sqlquery.h" +#include "db/db.h" +#include "pluginservicebase.h" +#include "sqlitestudio.h" +#include + +class ExportPlugin; +class QueryExecutor; +class ExportWorker; +class QBuffer; +class CfgEntry; + +/** + * @brief Provides database exporting capabilities. + * + * ExportManager is not thread-safe. Use it from single thread. + */ +class API_EXPORT ExportManager : public PluginServiceBase +{ + Q_OBJECT + public: + enum ExportMode + { + UNDEFINED = 0x00, + CLIPBOARD = 0x01, + DATABASE = 0x02, + TABLE = 0x04, + QUERY_RESULTS = 0x08, + FILE = 0x10 + }; + + Q_DECLARE_FLAGS(ExportModes, ExportMode) + + /** + * @brief Flags for requesting additional information for exporting by plugins. + * + * Each plugin implementation might ask ExportWorker to provide additional information for exporting. + * Such information is usually expensive operation (an additional database query to execute), therefore + * they are not enabled by default for all plugins. Each plugin has to ask for them individually + * by returning this enum values from ExportPlugin::getProviderFlags(). + * + * For each enum value returned from the ExportPlugin::getProviderFlags(), a single QHash entry will be prepared + * and that hash will be then passed to one of ExportPlugin::beforeExportQueryResults(), ExportPlugin::exportTable(), + * or ExportPlugin::exportVirtualTable(). If no flags were returned from ExportPlugin::getProviderFlags(), then + * empty hash will be passed to those methods. + * + * Each entry in the QHash has a key equal to one of values from this enum. Values from the hash are of QVariant type + * and therefore they need to be casted (by QVariant means) into desired type. For each enum value its description + * will tell you what actually is stored in the QVariant, so you can extract the information. + */ + enum ExportProviderFlag + { + NONE = 0x00, /**< This is a default. Nothing will be stored in the hash. */ + DATA_LENGTHS = 0x01, /**< + * Will provide maximum number of characters or bytes (depending on column type) + * for each exported table or qurey result column. It will be a QList<int>. + */ + ROW_COUNT = 0x02 /**< + * Will provide total number of rows that will be exported for the table or query results. + * It will be an integer value. + */ + }; + + Q_DECLARE_FLAGS(ExportProviderFlags, ExportProviderFlag) + + struct ExportObject + { + enum Type + { + TABLE, + INDEX, + TRIGGER, + VIEW + }; + + Type type; + QString database; // TODO fill when dbnames are fully supported + QString name; + QString ddl; + SqlQueryPtr data; + QHash providerData; + }; + + typedef QSharedPointer ExportObjectPtr; + + /** + * @brief Standard configuration for all exporting processes. + * + * Object of this type is passed to all exporting processes. + * It is configured with standard UI config for export. + */ + struct StandardExportConfig + { + /** + * @brief Text encoding. + * + * Always one of QTextCodec::availableCodecs(). + */ + QString codec; + + /** + * @brief Name of the file that the export being done to. + * + * This is provided just for information to the export process, + * but the plugin should use data stream provided to each called export method, + * instead of opening the file from this name. + * + * It will be null string if exporting is not performed into a file, but somewhere else + * (for example into a clipboard). + */ + QString outputFileName; + + /** + * @brief Indicates exporting to clipboard. + * + * This is just for an information, like outputFileName. Exporting methods will + * already have stream prepared for exporting to clipboard. + * + * Default is false. + */ + bool intoClipboard = false; + + /** + * @brief When exporting table or database, this indicates if table data should also be exported. + * + * Default is true. + */ + bool exportData = true; + + /** + * @brief When exporting only a table, this indicates if indexes related to that table should also be exported. + * + * Default is true. + */ + bool exportTableIndexes = true; + + /** + * @brief When exporting only a table, this indicates if triggers related to that table should also be exported. + * + * Default is true. + */ + bool exportTableTriggers = true; + }; + + /** + * @brief Standard export configuration options to be displayed on UI. + * + * Each of enum represents single property of StandardExportConfig which will be + * available on UI to configure. + */ + enum StandardConfigFlag + { + CODEC = 0x01, /**< Text encoding (see StandardExportConfig::codec). */ + }; + + Q_DECLARE_FLAGS(StandardConfigFlags, StandardConfigFlag) + + + explicit ExportManager(QObject *parent = 0); + ~ExportManager(); + + QStringList getAvailableFormats(ExportMode exportMode = UNDEFINED) const; + ExportPlugin* getPluginForFormat(const QString& formatName) const; + + /** + * @brief Configures export service for export. + * @param format Format to be used in upcoming export. + * @param config Standard configuration options to be used in upcoming export. + * + * ExportManager takes ownership of the config object. + * + * Call this method just befor any of export*() methods is called to prepare ExportManager for upcoming export process. + * Otherwise the export process will use settings from last configure() call. + * + * If any export is already in progress, this method reports error in logs and does nothing. + * If plugin for specified format cannot be found, then this method reports warning in logs and does nothing. + */ + void configure(const QString& format, StandardExportConfig* config); + + /** + * @brief Configures export service for export. + * @param format Format to be used in upcoming export. + * @param config Standard configuration options to be used in upcoming export. + * + * Same as method above, except it makes its own copy of the config object. + */ + void configure(const QString& format, const StandardExportConfig& config); + bool isExportInProgress() const; + void exportQueryResults(Db* db, const QString& query); + void exportTable(Db* db, const QString& database, const QString& table); + void exportDatabase(Db* db, const QStringList& objectListToExport); + + static bool isAnyPluginAvailable(); + + private: + void invalidFormat(const QString& format); + bool checkInitialConditions(); + QIODevice* getOutputStream(); + ExportWorker* prepareExport(); + void handleClipboardExport(); + + bool exportInProgress = false; + ExportMode mode; + StandardExportConfig* config = nullptr; + QString format; + ExportPlugin* plugin = nullptr; + QBuffer* bufferForClipboard = nullptr; + + public slots: + void interrupt(); + + private slots: + void finalizeExport(bool result, QIODevice* output); + + signals: + void exportFinished(); + void exportSuccessful(); + void exportFailed(); + void storeInClipboard(const QString& str); + void storeInClipboard(const QByteArray& bytes, const QString& mimeType); + void orderWorkerToInterrupt(); +}; + +#define EXPORT_MANAGER SQLITESTUDIO->getExportManager() + +Q_DECLARE_OPERATORS_FOR_FLAGS(ExportManager::StandardConfigFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(ExportManager::ExportModes) +Q_DECLARE_OPERATORS_FOR_FLAGS(ExportManager::ExportProviderFlags) + +#endif // EXPORTMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.cpp new file mode 100644 index 0000000..23fb513 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.cpp @@ -0,0 +1,28 @@ +#include "extralicensemanager.h" + +ExtraLicenseManager::ExtraLicenseManager() +{ +} + +bool ExtraLicenseManager::addLicense(const QString& title, const QString& filePath) +{ + if (licenses.contains(title)) + return false; + + licenses[title] = filePath; + return true; +} + +bool ExtraLicenseManager::removeLicense(const QString& title) +{ + if (!licenses.contains(title)) + return false; + + licenses.remove(title); + return true; +} + +const QHash&ExtraLicenseManager::getLicenses() const +{ + return licenses; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.h b/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.h new file mode 100644 index 0000000..fcf1203 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.h @@ -0,0 +1,21 @@ +#ifndef EXTRALICENSEMANAGER_H +#define EXTRALICENSEMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include +#include + +class API_EXPORT ExtraLicenseManager +{ + public: + ExtraLicenseManager(); + + bool addLicense(const QString& title, const QString& filePath); + bool removeLicense(const QString& title); + const QHash& getLicenses() const; + + private: + QHash licenses; +}; + +#endif // EXTRALISENCEMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.cpp new file mode 100644 index 0000000..10db318 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.cpp @@ -0,0 +1,39 @@ +#include "services/functionmanager.h" + +FunctionManager::FunctionBase::FunctionBase() +{ +} + +FunctionManager::FunctionBase::~FunctionBase() +{ +} + +QString FunctionManager::FunctionBase::toString() const +{ + static const QString format = "%1(%2)"; + QString args = undefinedArgs ? "..." : arguments.join(", "); + return format.arg(name).arg(args); +} + +QString FunctionManager::FunctionBase::typeString(Type type) +{ + switch (type) + { + case ScriptFunction::SCALAR: + return "SCALAR"; + case ScriptFunction::AGGREGATE: + return "AGGREGATE"; + } + return QString::null; +} + +FunctionManager::ScriptFunction::Type FunctionManager::FunctionBase::typeString(const QString& type) +{ + if (type == "SCALAR") + return ScriptFunction::SCALAR; + + if (type == "AGGREGATE") + return ScriptFunction::AGGREGATE; + + return ScriptFunction::SCALAR; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h new file mode 100644 index 0000000..b848c93 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h @@ -0,0 +1,74 @@ +#ifndef FUNCTIONMANAGER_H +#define FUNCTIONMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include +#include +#include +#include + +class Db; + +class API_EXPORT FunctionManager : public QObject +{ + Q_OBJECT + + public: + struct API_EXPORT FunctionBase + { + enum Type + { + SCALAR = 0, + AGGREGATE = 1 + }; + + FunctionBase(); + virtual ~FunctionBase(); + + virtual QString toString() const; + + static QString typeString(Type type); + static Type typeString(const QString& type); + + QString name; + QStringList arguments; + Type type = SCALAR; + bool undefinedArgs = true; + }; + + struct API_EXPORT ScriptFunction : public FunctionBase + { + QString lang; + QString code; + QString initCode; + QString finalCode; + QStringList databases; + bool allDatabases = true; + }; + + struct API_EXPORT NativeFunction : public FunctionBase + { + typedef std::function& args, Db* db, bool& ok)> ImplementationFunction; + + ImplementationFunction functionPtr; + }; + + virtual void setScriptFunctions(const QList& newFunctions) = 0; + virtual QList getAllScriptFunctions() const = 0; + virtual QList getScriptFunctionsForDatabase(const QString& dbName) const = 0; + virtual QList getAllNativeFunctions() const = 0; + + virtual QVariant evaluateScalar(const QString& name, int argCount, const QList& args, Db* db, bool& ok) = 0; + virtual void evaluateAggregateInitial(const QString& name, int argCount, Db* db, QHash& aggregateStorage) = 0; + virtual void evaluateAggregateStep(const QString& name, int argCount, const QList& args, Db* db, + QHash& aggregateStorage) = 0; + virtual QVariant evaluateAggregateFinal(const QString& name, int argCount, Db* db, bool& ok, QHash& aggregateStorage) = 0; + + signals: + void functionListChanged(); +}; + +#define FUNCTIONS SQLITESTUDIO->getFunctionManager() + +#endif // FUNCTIONMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp new file mode 100644 index 0000000..5876021 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp @@ -0,0 +1,125 @@ +#include "collationmanagerimpl.h" +#include "services/pluginmanager.h" +#include "plugins/scriptingplugin.h" +#include "services/notifymanager.h" +#include "services/dbmanager.h" +#include "common/utils.h" +#include + +CollationManagerImpl::CollationManagerImpl() +{ + init(); +} + +void CollationManagerImpl::setCollations(const QList& newCollations) +{ + collations = newCollations; + refreshCollationsByKey(); + storeInConfig(); + emit collationListChanged(); +} + +QList CollationManagerImpl::getAllCollations() const +{ + return collations; +} + +QList CollationManagerImpl::getCollationsForDatabase(const QString& dbName) const +{ + QList results; + foreach (const CollationPtr& coll, collations) + { + if (coll->allDatabases || coll->databases.contains(dbName, Qt::CaseInsensitive)) + results << coll; + } + return results; +} + +int CollationManagerImpl::evaluate(const QString& name, const QString& value1, const QString& value2) +{ + if (!collationsByKey.contains(name)) + { + qWarning() << "Could not find requested collation" << name << ", so using default collation."; + return evaluateDefault(value1, value2); + } + + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(collationsByKey[name]->lang); + if (!plugin) + { + qWarning() << "Plugin for collation" << name << ", not loaded, so using default collation."; + return evaluateDefault(value1, value2); + } + + QString err; + QVariant result = plugin->evaluate(collationsByKey[name]->code, {value1, value2}, &err); + + if (!err.isNull()) + { + qWarning() << "Error while evaluating collation:" << err; + return evaluateDefault(value1, value2); + } + + bool ok; + int intResult = result.toInt(&ok); + if (!ok) + { + qWarning() << "Not integer result from collation:" << result.toString(); + return evaluateDefault(value1, value2); + } + + return intResult; +} + +int CollationManagerImpl::evaluateDefault(const QString& value1, const QString& value2) +{ + return value1.compare(value2, Qt::CaseInsensitive); +} + +void CollationManagerImpl::init() +{ + loadFromConfig(); + refreshCollationsByKey(); +} + +void CollationManagerImpl::storeInConfig() +{ + QVariantList list; + QHash collHash; + for (CollationPtr coll : collations) + { + collHash["name"] = coll->name; + collHash["lang"] = coll->lang; + collHash["code"] = coll->code; + collHash["allDatabases"] = coll->allDatabases; + collHash["databases"] =common(DBLIST->getDbNames(), coll->databases); + list << collHash; + } + CFG_CORE.Internal.Collations.set(list); +} + +void CollationManagerImpl::loadFromConfig() +{ + collations.clear(); + + QVariantList list = CFG_CORE.Internal.Collations.get(); + QHash collHash; + CollationPtr coll; + for (const QVariant& var : list) + { + collHash = var.toHash(); + coll = CollationPtr::create(); + coll->name = collHash["name"].toString(); + coll->lang = collHash["lang"].toString(); + coll->code = collHash["code"].toString(); + coll->databases = collHash["databases"].toStringList(); + coll->allDatabases = collHash["allDatabases"].toBool(); + collations << coll; + } +} + +void CollationManagerImpl::refreshCollationsByKey() +{ + collationsByKey.clear(); + foreach (CollationPtr collation, collations) + collationsByKey[collation->name] = collation; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.h new file mode 100644 index 0000000..f5a5937 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.h @@ -0,0 +1,36 @@ +#ifndef COLLATIONMANAGERIMPL_H +#define COLLATIONMANAGERIMPL_H + +#include "services/collationmanager.h" + +class ScriptingPlugin; +class Plugin; +class PluginType; + +class API_EXPORT CollationManagerImpl : public CollationManager +{ + public: + CollationManagerImpl(); + + void setCollations(const QList& newCollations); + QList getAllCollations() const; + QList getCollationsForDatabase(const QString& dbName) const; + int evaluate(const QString& name, const QString& value1, const QString& value2); + int evaluateDefault(const QString& value1, const QString& value2); + + private: + void init(); + void storeInConfig(); + void loadFromConfig(); + void refreshCollationsByKey(); + + QList collations; + QHash collationsByKey; + QHash scriptingPlugins; + + private slots: + void pluginLoaded(Plugin* plugin, PluginType* type); + void pluginUnloaded(Plugin* plugin, PluginType* type); +}; + +#endif // COLLATIONMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp new file mode 100644 index 0000000..e210f01 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp @@ -0,0 +1,770 @@ +#include "configimpl.h" +#include "sqlhistorymodel.h" +#include "ddlhistorymodel.h" +#include "services/notifymanager.h" +#include "sqlitestudio.h" +#include "db/dbsqlite3.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static_qstring(DB_FILE_NAME, "settings3"); +qint64 ConfigImpl::sqlHistoryId = -1; + +ConfigImpl::~ConfigImpl() +{ + cleanUp(); +} + +void ConfigImpl::init() +{ + initDbFile(); + initTables(); + + connect(this, SIGNAL(sqlHistoryRefreshNeeded()), this, SLOT(refreshSqlHistory())); + connect(this, SIGNAL(ddlHistoryRefreshNeeded()), this, SLOT(refreshDdlHistory())); +} + +void ConfigImpl::cleanUp() +{ + if (db->isOpen()) + db->close(); + + safe_delete(db); +} + +const QString &ConfigImpl::getConfigDir() const +{ + return configDir; +} + +QString ConfigImpl::getConfigFilePath() const +{ + if (!db) + return QString(); + + return db->getPath(); +} + +void ConfigImpl::beginMassSave() +{ + if (isMassSaving()) + return; + + emit massSaveBegins(); + db->exec("BEGIN;"); + massSaving = true; +} + +void ConfigImpl::commitMassSave() +{ + if (!isMassSaving()) + return; + + db->exec("COMMIT;"); + emit massSaveCommited(); + massSaving = false; +} + +void ConfigImpl::rollbackMassSave() +{ + if (!isMassSaving()) + return; + + db->exec("ROLLBACK;"); + massSaving = false; +} + +bool ConfigImpl::isMassSaving() const +{ + return massSaving; +} + +void ConfigImpl::set(const QString &group, const QString &key, const QVariant &value) +{ + QByteArray bytes; + QDataStream stream(&bytes, QIODevice::WriteOnly); + stream << value; + + db->exec("INSERT OR REPLACE INTO settings VALUES (?, ?, ?)", {group, key, bytes}); +} + +QVariant ConfigImpl::get(const QString &group, const QString &key) +{ + SqlQueryPtr results = db->exec("SELECT value FROM settings WHERE [group] = ? AND [key] = ?", {group, key}); + return deserializeValue(results->getSingleCell()); +} + +QHash ConfigImpl::getAll() +{ + SqlQueryPtr results = db->exec("SELECT [group], [key], value FROM settings"); + + QHash cfg; + QString key; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + key = row->value("group").toString() + "." + row->value("key").toString(); + cfg[key] = deserializeValue(row->value("value")); + } + return cfg; +} + +bool ConfigImpl::storeErrorAndReturn(SqlQueryPtr results) +{ + if (results->isError()) + { + lastQueryError = results->getErrorText(); + return true; + } + else + return false; +} + +void ConfigImpl::printErrorIfSet(SqlQueryPtr results) +{ + if (results && results->isError()) + { + qCritical() << "Config error while executing query:" << results->getErrorText(); + storeErrorAndReturn(results); + } +} + +bool ConfigImpl::addDb(const QString& name, const QString& path, const QHash& options) +{ + QByteArray optBytes = hashToBytes(options); + SqlQueryPtr results = db->exec("INSERT INTO dblist VALUES (?, ?, ?)", {name, path, optBytes}); + return !storeErrorAndReturn(results); +} + +bool ConfigImpl::updateDb(const QString &name, const QString &newName, const QString &path, const QHash &options) +{ + QByteArray optBytes = hashToBytes(options); + SqlQueryPtr results = db->exec("UPDATE dblist SET name = ?, path = ?, options = ? WHERE name = ?", + {newName, path, optBytes, name}); + + return (!storeErrorAndReturn(results) && results->rowsAffected() > 0); +} + +bool ConfigImpl::removeDb(const QString &name) +{ + SqlQueryPtr results = db->exec("DELETE FROM dblist WHERE name = ?", {name}); + return (!storeErrorAndReturn(results) && results->rowsAffected() > 0); +} + +bool ConfigImpl::isDbInConfig(const QString &name) +{ + SqlQueryPtr results = db->exec("SELECT * FROM dblist WHERE name = ?", {name}); + return (!storeErrorAndReturn(results) && results->hasNext()); +} + +QString ConfigImpl::getLastErrorString() const +{ + QString msg = db->getErrorText(); + if (msg.trimmed().isEmpty()) + return lastQueryError; + + return msg; +} + +QList ConfigImpl::dbList() +{ + QList entries; + SqlQueryPtr results = db->exec("SELECT name, path, options FROM dblist"); + CfgDbPtr cfgDb; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + cfgDb = CfgDbPtr::create(); + cfgDb->name = row->value("name").toString(); + cfgDb->path = row->value("path").toString(); + cfgDb->options = deserializeValue(row->value("options")).toHash(); + entries += cfgDb; + } + + return entries; +} + +ConfigImpl::CfgDbPtr ConfigImpl::getDb(const QString& dbName) +{ + SqlQueryPtr results = db->exec("SELECT path, options FROM dblist WHERE name = ?", {dbName}); + + if (!results->hasNext()) + return CfgDbPtr(); + + SqlResultsRowPtr row = results->next(); + + CfgDbPtr cfgDb = CfgDbPtr::create(); + cfgDb->name = dbName; + cfgDb->path = row->value("path").toString(); + cfgDb->options = deserializeValue(row->value("options")).toHash(); + return cfgDb; +} + +void ConfigImpl::storeGroups(const QList& groups) +{ + db->begin(); + db->exec("DELETE FROM groups"); + + foreach (const DbGroupPtr& group, groups) + storeGroup(group); + + db->commit(); +} + +void ConfigImpl::storeGroup(const ConfigImpl::DbGroupPtr &group, qint64 parentId) +{ + QVariant parent = QVariant(QVariant::LongLong); + if (parentId > -1) + parent = parentId; + + SqlQueryPtr results = db->exec("INSERT INTO groups (name, [order], parent, open, dbname) VALUES (?, ?, ?, ?, ?)", + {group->name, group->order, parent, group->open, group->referencedDbName}); + + qint64 newParentId = results->getRegularInsertRowId(); + foreach (const DbGroupPtr& childGroup, group->childs) + storeGroup(childGroup, newParentId); +} + +QList ConfigImpl::getGroups() +{ + DbGroupPtr topGroup = DbGroupPtr::create(); + topGroup->id = -1; + readGroupRecursively(topGroup); + return topGroup->childs; +} + +ConfigImpl::DbGroupPtr ConfigImpl::getDbGroup(const QString& dbName) +{ + SqlQueryPtr results = db->exec("SELECT id, name, [order], open, dbname FROM groups WHERE dbname = ? LIMIT 1", {dbName}); + + DbGroupPtr group = DbGroupPtr::create(); + group->referencedDbName = dbName; + + if (!results->hasNext()) + return group; + + SqlResultsRowPtr row = results->next(); + group->id = row->value("id").toULongLong(); + group->name = row->value("name").toString(); + group->order = row->value("order").toInt(); + group->open = row->value("open").toBool(); + return group; +} + +qint64 ConfigImpl::addSqlHistory(const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) +{ + if (sqlHistoryId < 0) + { + SqlQueryPtr results = db->exec("SELECT max(id) FROM sqleditor_history"); + if (results->isError()) + { + qCritical() << "Cannot add SQL history, because cannot determinate sqleditor_history max(id):" << results->getErrorText(); + return -1; + } + + if (results->hasNext()) + sqlHistoryId = results->getSingleCell().toLongLong() + 1; + else + sqlHistoryId = 0; + } + + QtConcurrent::run(this, &ConfigImpl::asyncAddSqlHistory, sqlHistoryId, sql, dbName, timeSpentMillis, rowsAffected); + sqlHistoryId++; + return sqlHistoryId; +} + +void ConfigImpl::updateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) +{ + QtConcurrent::run(this, &ConfigImpl::asyncUpdateSqlHistory, id, sql, dbName, timeSpentMillis, rowsAffected); +} + +void ConfigImpl::clearSqlHistory() +{ + QtConcurrent::run(this, &ConfigImpl::asyncClearSqlHistory); +} + +QAbstractItemModel* ConfigImpl::getSqlHistoryModel() +{ + if (!sqlHistoryModel) + sqlHistoryModel = new SqlHistoryModel(db, this); + + return sqlHistoryModel; +} + +void ConfigImpl::addCliHistory(const QString& text) +{ + QtConcurrent::run(this, &ConfigImpl::asyncAddCliHistory, text); +} + +void ConfigImpl::applyCliHistoryLimit() +{ + QtConcurrent::run(this, &ConfigImpl::asyncApplyCliHistoryLimit); +} + +void ConfigImpl::clearCliHistory() +{ + QtConcurrent::run(this, &ConfigImpl::asyncClearCliHistory); +} + +QStringList ConfigImpl::getCliHistory() const +{ + static_qstring(selectQuery, "SELECT text FROM cli_history ORDER BY id"); + + SqlQueryPtr results = db->exec(selectQuery); + if (results->isError()) + qWarning() << "Error while getting CLI history:" << db->getErrorText(); + + return results->columnAsList("text"); +} + +void ConfigImpl::addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) +{ + QtConcurrent::run(this, &ConfigImpl::asyncAddDdlHistory, queries, dbName, dbFile); +} + +QList ConfigImpl::getDdlHistoryFor(const QString& dbName, const QString& dbFile, const QDate& date) +{ + static_qstring(sql, + "SELECT timestamp," + " queries" + " FROM ddl_history" + " WHERE dbname = ?" + " AND file = ?" + " AND date(timestamp, 'unixepoch') = ?"); + + SqlQueryPtr results = db->exec(sql, {dbName, dbFile, date.toString("yyyy-MM-dd")}); + + QList entries; + DdlHistoryEntryPtr entry; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + entry = DdlHistoryEntryPtr::create(); + entry->dbName = dbName; + entry->dbFile = dbFile; + entry->timestamp = QDateTime::fromTime_t(row->value("timestamp").toUInt()); + entry->queries = row->value("queries").toString(); + entries << entry; + } + return entries; +} + +DdlHistoryModel* ConfigImpl::getDdlHistoryModel() +{ + if (!ddlHistoryModel) + ddlHistoryModel = new DdlHistoryModel(db, this); + + return ddlHistoryModel; +} + +void ConfigImpl::clearDdlHistory() +{ + QtConcurrent::run(this, &ConfigImpl::asyncClearDdlHistory); +} + +void ConfigImpl::addReportHistory(bool isFeatureRequest, const QString& title, const QString& url) +{ + QtConcurrent::run(this, &ConfigImpl::asyncAddReportHistory, isFeatureRequest, title, url); +} + +QList ConfigImpl::getReportHistory() +{ + static_qstring(sql, "SELECT id, timestamp, title, url, feature_request FROM reports_history"); + + SqlQueryPtr results = db->exec(sql); + + QList entries; + SqlResultsRowPtr row; + ReportHistoryEntryPtr entry; + while (results->hasNext()) + { + row = results->next(); + entry = ReportHistoryEntryPtr::create(); + entry->id = row->value("id").toInt(); + entry->timestamp = row->value("timestamp").toInt(); + entry->title = row->value("title").toString(); + entry->url = row->value("url").toString(); + entry->isFeatureRequest = row->value("feature_request").toBool(); + entries << entry; + } + return entries; +} + +void ConfigImpl::deleteReport(int id) +{ + QtConcurrent::run(this, &ConfigImpl::asyncDeleteReport, id); +} + +void ConfigImpl::clearReportHistory() +{ + QtConcurrent::run(this, &ConfigImpl::asyncClearReportHistory); +} + +void ConfigImpl::readGroupRecursively(ConfigImpl::DbGroupPtr group) +{ + SqlQueryPtr results; + if (group->id < 0) + results = db->exec("SELECT id, name, [order], open, dbname FROM groups WHERE parent IS NULL ORDER BY [order]"); + else + results = db->exec("SELECT id, name, [order], open, dbname FROM groups WHERE parent = ? ORDER BY [order]", {group->id}); + + DbGroupPtr childGroup; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + childGroup = DbGroupPtr::create(); + childGroup->id = row->value("id").toULongLong(); + childGroup->name = row->value("name").toString(); + childGroup->order = row->value("order").toInt(); + childGroup->open = row->value("open").toBool(); + childGroup->referencedDbName = row->value("dbname").toString(); + group->childs += childGroup; + } + + for (int i = 0; i < group->childs.size(); i++) + readGroupRecursively(group->childs[i]); +} + +void ConfigImpl::begin() +{ + db->begin(); +} + +void ConfigImpl::commit() +{ + db->commit(); +} + +void ConfigImpl::rollback() +{ + db->rollback(); +} + +QString ConfigImpl::getConfigPath() +{ +#ifdef Q_OS_WIN + if (QSysInfo::windowsVersion() & QSysInfo::WV_NT_based) + return SQLITESTUDIO->getEnv("APPDATA")+"/sqlitestudio"; + else + return SQLITESTUDIO->getEnv("HOME")+"/sqlitestudio"; +#else + return SQLITESTUDIO->getEnv("HOME")+"/.config/sqlitestudio"; +#endif +} + +QString ConfigImpl::getPortableConfigPath() +{ + QFileInfo file; + QDir dir("./sqlitestudio-cfg"); + + file = QFileInfo(dir.absolutePath()); + if (!file.exists()) + return dir.absolutePath(); + + if (!file.isDir() || !file.isReadable() || !file.isWritable()) + return QString::null; + + foreach (file, dir.entryInfoList()) + { + if (!file.isReadable() || !file.isWritable()) + return QString::null; + } + + return dir.absolutePath(); +} + +void ConfigImpl::initTables() +{ + SqlQueryPtr results = db->exec("SELECT lower(name) AS name FROM sqlite_master WHERE type = 'table'"); + QList tables = results->columnAsList(0); + + if (!tables.contains("version")) + { + QString table; + foreach (table, tables) + db->exec("DROP TABLE "+table); + + tables.clear(); + db->exec("CREATE TABLE version (version NUMERIC)"); + db->exec("INSERT INTO version VALUES ("+QString::number(SQLITESTUDIO_CONFIG_VERSION)+")"); + } + + if (!tables.contains("settings")) + db->exec("CREATE TABLE settings ([group] TEXT, [key] TEXT, value, PRIMARY KEY([group], [key]))"); + + if (!tables.contains("sqleditor_history")) + db->exec("CREATE TABLE sqleditor_history (id INTEGER PRIMARY KEY, dbname TEXT, date INTEGER, time_spent INTEGER, rows INTEGER, sql TEXT)"); + + if (!tables.contains("dblist")) + db->exec("CREATE TABLE dblist (name TEXT PRIMARY KEY, path TEXT UNIQUE, options TEXT)"); + + if (!tables.contains("groups")) + db->exec("CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, parent INTEGER REFERENCES groups(id), " + "[order] INTEGER, open INTEGER DEFAULT 0, dbname TEXT UNIQUE REFERENCES dblist(name) ON UPDATE CASCADE ON DELETE CASCADE, " + "UNIQUE(name, parent))"); + + if (!tables.contains("ddl_history")) + db->exec("CREATE TABLE ddl_history (id INTEGER PRIMARY KEY AUTOINCREMENT, dbname TEXT, file TEXT, timestamp INTEGER, " + "queries TEXT)"); + + if (!tables.contains("cli_history")) + db->exec("CREATE TABLE cli_history (id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT)"); + + if (!tables.contains("reports_history")) + db->exec("CREATE TABLE reports_history (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, feature_request BOOLEAN, title TEXT, url TEXT)"); +} + +void ConfigImpl::initDbFile() +{ + // Determinate global config location and portable one + QString globalPath = getConfigPath(); + QString portablePath = getPortableConfigPath(); + + QStringList paths; + if (!globalPath.isNull() && !portablePath.isNull()) + { + if (QFileInfo(portablePath).exists()) + { + paths << portablePath+"/"+DB_FILE_NAME; + paths << globalPath+"/"+DB_FILE_NAME; + } + else + { + paths << globalPath+"/"+DB_FILE_NAME; + paths << portablePath+"/"+DB_FILE_NAME; + } + } + else if (!globalPath.isNull()) + { + paths << globalPath+"/"+DB_FILE_NAME; + } + else if (!portablePath.isNull()) + { + paths << portablePath+"/"+DB_FILE_NAME; + } + + // Create global config directory if not existing + QDir dir; + if (!globalPath.isNull()) + { + dir = QDir(globalPath); + if (!dir.exists()) + QDir::root().mkpath(globalPath); + } + + // A fallback to in-memory db + paths << ":memory:"; + + // Go through all candidates and pick one + QString path; + foreach (path, paths) + { + dir = QDir(path); + if (path != ":memory:") + dir.cdUp(); + + if (tryInitDbFile(path)) + { + configDir = dir.absolutePath(); + break; + } + } + + // We ended up with in-memory one? That's not good. + if (configDir == ":memory:") + { + paths.removeLast(); + notifyError(QObject::tr("Could not initialize configuration file. Any configuration changes and queries history will be lost after application restart." + " Tried to initialize the file at following localizations: %1.").arg(paths.join(", "))); + } + + qDebug() << "Using configuration directory:" << configDir; + db->exec("PRAGMA foreign_keys = 1;"); +} + +bool ConfigImpl::tryInitDbFile(const QString &dbPath) +{ + db = new DbSqlite3("SQLiteStudio settings", dbPath, {{DB_PURE_INIT, true}}); + if (!db->open()) + { + safe_delete(db); + return false; + } + + SqlQueryPtr results = db->exec("SELECT * FROM sqlite_master"); + if (results->isError()) + { + safe_delete(db); + return false; + } + + return true; +} + +QVariant ConfigImpl::deserializeValue(const QVariant &value) +{ + if (!value.isValid()) + return QVariant(); + + QByteArray bytes = value.toByteArray(); + if (bytes.isNull()) + return QVariant(); + + QVariant deserializedValue; + QDataStream stream(bytes); + stream >> deserializedValue; + return deserializedValue; +} + +void ConfigImpl::asyncAddSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) +{ + db->begin(); + SqlQueryPtr results = db->exec("INSERT INTO sqleditor_history (id, dbname, date, time_spent, rows, sql) VALUES (?, ?, ?, ?, ?, ?)", + {id, dbName, (QDateTime::currentMSecsSinceEpoch() / 1000), timeSpentMillis, rowsAffected, sql}); + + if (results->isError()) + { + qDebug() << "Error adding SQL history:" << results->getErrorText(); + db->rollback(); + return; + } + + int maxHistorySize = CFG_CORE.General.SqlHistorySize.get(); + + results = db->exec("SELECT count(*) FROM sqleditor_history"); + if (results->hasNext() && results->getSingleCell().toInt() > maxHistorySize) + { + results = db->exec(QString("SELECT id FROM sqleditor_history ORDER BY id DESC LIMIT 1 OFFSET %1").arg(maxHistorySize)); + if (results->hasNext()) + { + int id = results->getSingleCell().toInt(); + if (id > 0) // it will be 0 on fail conversion, but we won't delete id <= 0 ever. + db->exec("DELETE FROM sqleditor_history WHERE id <= ?", {id}); + } + } + db->commit(); + + emit sqlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncUpdateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) +{ + db->exec("UPDATE sqleditor_history SET dbname = ?, time_spent = ?, rows = ?, sql = ? WHERE id = ?", + {dbName, timeSpentMillis, rowsAffected, sql, id}); + + emit sqlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncClearSqlHistory() +{ + db->exec("DELETE FROM sqleditor_history"); + emit sqlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncAddCliHistory(const QString& text) +{ + static_qstring(insertQuery, "INSERT INTO cli_history (text) VALUES (?)"); + + SqlQueryPtr results = db->exec(insertQuery, {text}); + if (results->isError()) + qWarning() << "Error while adding CLI history:" << results->getErrorText(); + + applyCliHistoryLimit(); +} + +void ConfigImpl::asyncApplyCliHistoryLimit() +{ + static_qstring(limitQuery, "DELETE FROM cli_history WHERE id >= (SELECT id FROM cli_history ORDER BY id LIMIT 1 OFFSET %1)"); + + SqlQueryPtr results = db->exec(limitQuery.arg(CFG_CORE.Console.HistorySize.get())); + if (results->isError()) + qWarning() << "Error while limiting CLI history:" << db->getErrorText(); +} + +void ConfigImpl::asyncClearCliHistory() +{ + static_qstring(clearQuery, "DELETE FROM cli_history"); + + SqlQueryPtr results = db->exec(clearQuery); + if (results->isError()) + qWarning() << "Error while clearing CLI history:" << db->getErrorText(); +} + +void ConfigImpl::asyncAddDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) +{ + static_qstring(insert, "INSERT INTO ddl_history (dbname, file, timestamp, queries) VALUES (?, ?, ?, ?)"); + static_qstring(countSql, "SELECT count(*) FROM ddl_history"); + static_qstring(idSql, "SELECT id FROM ddl_history ORDER BY id DESC LIMIT 1 OFFSET %1"); + static_qstring(deleteSql, "DELETE FROM ddl_history WHERE id <= ?"); + + db->begin(); + db->exec(insert, {dbName, dbFile, QDateTime::currentDateTime().toTime_t(), queries}); + + int maxHistorySize = CFG_CORE.General.DdlHistorySize.get(); + + SqlQueryPtr results = db->exec(countSql); + if (results->hasNext() && results->getSingleCell().toInt() > maxHistorySize) + { + results = db->exec(QString(idSql).arg(maxHistorySize), Db::Flag::NO_LOCK); + if (results->hasNext()) + { + int id = results->getSingleCell().toInt(); + if (id > 0) // it will be 0 on fail conversion, but we won't delete id <= 0 ever. + db->exec(deleteSql, {id}); + } + } + db->commit(); + + emit ddlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncClearDdlHistory() +{ + db->exec("DELETE FROM ddl_history"); + emit ddlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncAddReportHistory(bool isFeatureRequest, const QString& title, const QString& url) +{ + static_qstring(sql, "INSERT INTO reports_history (feature_request, timestamp, title, url) VALUES (?, ?, ?, ?)"); + db->exec(sql, {(isFeatureRequest ? 1 : 0), QDateTime::currentDateTime().toTime_t(), title, url}); + emit reportsHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncDeleteReport(int id) +{ + static_qstring(sql, "DELETE FROM reports_history WHERE id = ?"); + db->exec(sql, {id}); + emit reportsHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncClearReportHistory() +{ + static_qstring(sql, "DELETE FROM reports_history"); + db->exec(sql); + emit reportsHistoryRefreshNeeded(); +} + +void ConfigImpl::refreshSqlHistory() +{ + if (sqlHistoryModel) + sqlHistoryModel->refresh(); +} + +void ConfigImpl::refreshDdlHistory() +{ + if (ddlHistoryModel) + ddlHistoryModel->refresh(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h new file mode 100644 index 0000000..ec32e8d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h @@ -0,0 +1,127 @@ +#ifndef CONFIGIMPL_H +#define CONFIGIMPL_H + +#include "coreSQLiteStudio_global.h" +#include "services/config.h" +#include "db/sqlquery.h" + +class AsyncConfigHandler; +class SqlHistoryModel; + +class API_EXPORT ConfigImpl : public Config +{ + Q_OBJECT + + friend class AsyncConfigHandler; + + public: + virtual ~ConfigImpl(); + + void init(); + void cleanUp(); + const QString& getConfigDir() const; + QString getConfigFilePath() const; + + void beginMassSave(); + void commitMassSave(); + void rollbackMassSave(); + bool isMassSaving() const; + void set(const QString& group, const QString& key, const QVariant& value); + QVariant get(const QString& group, const QString& key); + QHash getAll(); + + bool addDb(const QString& name, const QString& path, const QHash &options); + bool updateDb(const QString& name, const QString &newName, const QString& path, const QHash &options); + bool removeDb(const QString& name); + bool isDbInConfig(const QString& name); + QString getLastErrorString() const; + + /** + * @brief Provides list of all registered databases. + * @return List of database entries. + * + * Registered databases are those that user added to the application. They are not necessary valid or supported. + * They can be inexisting or unsupported, but they are kept in registry in case user fixes file path, + * or loads plugin to support it. + */ + QList dbList(); + CfgDbPtr getDb(const QString& dbName); + + void storeGroups(const QList& groups); + QList getGroups(); + DbGroupPtr getDbGroup(const QString& dbName); + + qint64 addSqlHistory(const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); + void updateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); + void clearSqlHistory(); + QAbstractItemModel* getSqlHistoryModel(); + + void addCliHistory(const QString& text); + void applyCliHistoryLimit(); + void clearCliHistory(); + QStringList getCliHistory() const; + + void addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile); + QList getDdlHistoryFor(const QString& dbName, const QString& dbFile, const QDate& date); + DdlHistoryModel* getDdlHistoryModel(); + void clearDdlHistory(); + + void addReportHistory(bool isFeatureRequest, const QString& title, const QString& url); + QList getReportHistory(); + void deleteReport(int id); + void clearReportHistory(); + + void begin(); + void commit(); + void rollback(); + + private: + /** + * @brief Stores error from query in class member. + * @param query Query to get error from. + * @return true if the query had any error set, or false if not. + * + * If the error was defined in the query, its message is stored in lastQueryError. + */ + bool storeErrorAndReturn(SqlQueryPtr results); + void printErrorIfSet(SqlQueryPtr results); + void storeGroup(const DbGroupPtr& group, qint64 parentId = -1); + void readGroupRecursively(DbGroupPtr group); + QString getConfigPath(); + QString getPortableConfigPath(); + void initTables(); + void initDbFile(); + bool tryInitDbFile(const QString& dbPath); + QVariant deserializeValue(const QVariant& value); + + void asyncAddSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); + void asyncUpdateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); + void asyncClearSqlHistory(); + + void asyncAddCliHistory(const QString& text); + void asyncApplyCliHistoryLimit(); + void asyncClearCliHistory(); + + void asyncAddDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile); + void asyncClearDdlHistory(); + + void asyncAddReportHistory(bool isFeatureRequest, const QString& title, const QString& url); + void asyncDeleteReport(int id); + void asyncClearReportHistory(); + + static Config* instance; + static qint64 sqlHistoryId; + + Db* db = nullptr; + QString configDir; + QString lastQueryError; + bool massSaving = false; + SqlHistoryModel* sqlHistoryModel = nullptr; + DdlHistoryModel* ddlHistoryModel = nullptr; + + public slots: + void refreshDdlHistory(); + void refreshSqlHistory(); +}; + +#endif // CONFIGIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp new file mode 100644 index 0000000..43fc953 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp @@ -0,0 +1,524 @@ +#include "dbmanagerimpl.h" +#include "db/db.h" +#include "services/config.h" +#include "plugins//dbplugin.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" +#include "common/utils.h" +#include +#include +#include +#include +#include +#include +#include +#include + +DbManagerImpl::DbManagerImpl(QObject *parent) : + DbManager(parent) +{ + init(); +} + +DbManagerImpl::~DbManagerImpl() +{ + foreach (Db* db, dbList) + { + disconnect(db, SIGNAL(disconnected()), this, SLOT(dbDisconnectedSlot())); + disconnect(db, SIGNAL(aboutToDisconnect(bool&)), this, SLOT(dbAboutToDisconnect(bool&))); + if (db->isOpen()) + db->close(); + + delete db; + } + dbList.clear(); + nameToDb.clear(); + pathToDb.clear(); +} + +bool DbManagerImpl::addDb(const QString &name, const QString &path, bool permanent) +{ + return addDb(name, path, QHash(), permanent); +} + +bool DbManagerImpl::addDb(const QString &name, const QString &path, const QHash& options, bool permanent) +{ + if (getByName(name)) + { + qWarning() << "Tried to add database with name that was already on the list:" << name; + return false; // db with this name exists + } + + QString errorMessage; + Db* db = createDb(name, path, options, &errorMessage); + if (!db) + { + notifyError(tr("Could not add database %1: %2").arg(path).arg(errorMessage)); + return false; + } + + listLock.lockForWrite(); + addDbInternal(db, permanent); + listLock.unlock(); + + emit dbAdded(db); + + return true; +} + +bool DbManagerImpl::updateDb(Db* db, const QString &name, const QString &path, const QHash &options, bool permanent) +{ + if (db->isOpen()) + { + if (!db->close()) + return false; + } + + listLock.lockForWrite(); + nameToDb.remove(db->getName(), Qt::CaseInsensitive); + pathToDb.remove(db->getPath()); + + bool pathDifferent = db->getPath() != path; + + QString oldName = db->getName(); + db->setName(name); + db->setPath(path); + db->setConnectionOptions(options); + + bool result = false; + if (permanent) + { + if (CFG->isDbInConfig(oldName)) + result = CFG->updateDb(oldName, name, path, options); + else + result = CFG->addDb(name, path, options); + } + else if (CFG->isDbInConfig(name)) // switched "permanent" off? + result = CFG->removeDb(name); + + InvalidDb* invalidDb = dynamic_cast(db); + Db* reloadedDb = db; + if (pathDifferent && invalidDb) + reloadedDb = tryToLoadDb(invalidDb); + + if (reloadedDb) // reloading was not necessary (was not invalid) or it was successful + db = reloadedDb; + + nameToDb[name] = db; + pathToDb[path] = db; + + listLock.unlock(); + + if (result && reloadedDb) + emit dbUpdated(oldName, db); + else if (reloadedDb) // database reloaded correctly, but update failed + notifyError(tr("Database %1 could not be updated, because of an error: %2").arg(oldName).arg(CFG->getLastErrorString())); + + return result; +} + +void DbManagerImpl::removeDbByName(const QString &name, Qt::CaseSensitivity cs) +{ + listLock.lockForRead(); + bool contains = nameToDb.contains(name, cs); + listLock.unlock(); + + if (!contains) + return; + + listLock.lockForWrite(); + Db* db = nameToDb[name]; + removeDbInternal(db); + listLock.unlock(); + + emit dbRemoved(db); + + delete db; +} + +void DbManagerImpl::removeDbByPath(const QString &path) +{ + listLock.lockForRead(); + bool contains = pathToDb.contains(path); + listLock.unlock(); + if (!contains) + return; + + listLock.lockForWrite(); + Db* db = pathToDb[path]; + removeDbInternal(db); + listLock.unlock(); + + emit dbRemoved(db); + + delete db; +} + +void DbManagerImpl::removeDb(Db* db) +{ + db->close(); + + listLock.lockForWrite(); + removeDbInternal(db); + listLock.unlock(); + + emit dbRemoved(db); + delete db; +} + +void DbManagerImpl::removeDbInternal(Db* db, bool alsoFromConfig) +{ + QString name = db->getName(); + if (alsoFromConfig) + CFG->removeDb(name); + + nameToDb.remove(name); + pathToDb.remove(db->getPath()); + dbList.removeOne(db); + disconnect(db, SIGNAL(connected()), this, SLOT(dbConnectedSlot())); + disconnect(db, SIGNAL(disconnected()), this, SLOT(dbDisconnectedSlot())); + disconnect(db, SIGNAL(aboutToDisconnect(bool&)), this, SLOT(dbAboutToDisconnect(bool&))); +} + +QList DbManagerImpl::getDbList() +{ + listLock.lockForRead(); + QList list = dbList; + listLock.unlock(); + return list; +} + +QList DbManagerImpl::getValidDbList() +{ + QList list = getDbList(); + QMutableListIterator it(list); + while (it.hasNext()) + { + it.next(); + if (!it.value()->isValid()) + it.remove(); + } + + return list; +} + +QList DbManagerImpl::getConnectedDbList() +{ + QList list = getDbList(); + QMutableListIterator it(list); + while (it.hasNext()) + { + it.next(); + if (!it.value()->isOpen()) + it.remove(); + } + + return list; +} + +QStringList DbManagerImpl::getDbNames() +{ + QReadLocker lock(&listLock); + return nameToDb.keys(); +} + +Db* DbManagerImpl::getByName(const QString &name, Qt::CaseSensitivity cs) +{ + QReadLocker lock(&listLock); + return nameToDb.value(name, cs); +} + +Db* DbManagerImpl::getByPath(const QString &path) +{ + return pathToDb.value(path); +} + +Db* DbManagerImpl::createInMemDb() +{ + if (!inMemDbCreatorPlugin) + return nullptr; + + return inMemDbCreatorPlugin->getInstance("", ":memory:", {}); +} + +bool DbManagerImpl::isTemporary(Db* db) +{ + return CFG->getDb(db->getName()).isNull(); +} + +QString DbManagerImpl::quickAddDb(const QString& path, const QHash& options) +{ + QString newName = DbManager::generateDbName(path); + newName = generateUniqueName(newName, DBLIST->getDbNames()); + if (!DBLIST->addDb(newName, path, options, false)) + return QString::null; + + return newName; +} + +void DbManagerImpl::setInMemDbCreatorPlugin(DbPlugin* plugin) +{ + inMemDbCreatorPlugin = plugin; +} + +void DbManagerImpl::init() +{ + Q_ASSERT(PLUGINS); + + loadInitialDbList(); + + connect(PLUGINS, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(aboutToUnload(Plugin*,PluginType*))); + connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(loaded(Plugin*,PluginType*))); +} + +void DbManagerImpl::loadInitialDbList() +{ + QUrl url; + InvalidDb* db = nullptr; + foreach (const Config::CfgDbPtr& cfgDb, CFG->dbList()) + { + db = new InvalidDb(cfgDb->name, cfgDb->path, cfgDb->options); + + url = QUrl::fromUserInput(cfgDb->path); + if (url.isLocalFile() && !QFile::exists(cfgDb->path)) + db->setError(tr("Database file doesn't exist.")); + else + db->setError(tr("No supporting plugin loaded.")); + + addDbInternal(db, false); + } +} + +void DbManagerImpl::notifyDatabasesAreLoaded() +{ + // Any databases were already loaded by loaded() slot, which is called when DbPlugin was loaded. + emit dbListLoaded(); +} + +void DbManagerImpl::scanForNewDatabasesInConfig() +{ + QList cfgDbList = CFG->dbList(); + + QUrl url; + InvalidDb* db = nullptr; + for (const Config::CfgDbPtr& cfgDb : cfgDbList) + { + if (getByName(cfgDb->name) || getByPath(cfgDb->path)) + continue; + + db = new InvalidDb(cfgDb->name, cfgDb->path, cfgDb->options); + + url = QUrl::fromUserInput(cfgDb->path); + if (url.isLocalFile() && !QFile::exists(cfgDb->path)) + db->setError(tr("Database file doesn't exist.")); + else + db->setError(tr("No supporting plugin loaded.")); + + addDbInternal(db); + tryToLoadDb(db); + } +} + +void DbManagerImpl::addDbInternal(Db* db, bool alsoToConfig) +{ + if (alsoToConfig) + CFG->addDb(db->getName(), db->getPath(), db->getConnectionOptions()); + + dbList << db; + nameToDb[db->getName()] = db; + pathToDb[db->getPath()] = db; + connect(db, SIGNAL(connected()), this, SLOT(dbConnectedSlot())); + connect(db, SIGNAL(disconnected()), this, SLOT(dbDisconnectedSlot())); + connect(db, SIGNAL(aboutToDisconnect(bool&)), this, SLOT(dbAboutToDisconnect(bool&))); +} + +QList DbManagerImpl::getInvalidDatabases() const +{ + return filter(dbList, [](Db* db) -> bool + { + return !db->isValid(); + }); +} + +Db* DbManagerImpl::tryToLoadDb(InvalidDb* invalidDb) +{ + QUrl url = QUrl::fromUserInput(invalidDb->getPath()); + if (url.isLocalFile() && !QFile::exists(invalidDb->getPath())) + return nullptr; + + Db* db = createDb(invalidDb->getName(), invalidDb->getPath(), invalidDb->getConnectionOptions()); + if (!db) + return nullptr; + + removeDbInternal(invalidDb, false); + delete invalidDb; + + addDbInternal(db, false); + + if (CFG->getDbGroup(db->getName())->open) + db->open(); + + emit dbLoaded(db); + return db; +} + +Db* DbManagerImpl::createDb(const QString &name, const QString &path, const QHash &options, QString* errorMessages) +{ + QList dbPlugins = PLUGINS->getLoadedPlugins(); + DbPlugin* dbPlugin = nullptr; + Db* db = nullptr; + QStringList messages; + QString message; + foreach (dbPlugin, dbPlugins) + { + if (options.contains("plugin") && options["plugin"] != dbPlugin->getName()) + continue; + + db = dbPlugin->getInstance(name, path, options, &message); + if (!db) + { + messages << message; + continue; + } + + if (!db->initAfterCreated()) + { + messages << tr("Database could not be initialized."); + continue; + } + + return db; + } + + if (errorMessages) + { + if (messages.size() == 0) + messages << tr("No suitable database driver plugin found."); + + *errorMessages = messages.join("; "); + } + + return nullptr; +} + + +void DbManagerImpl::dbConnectedSlot() +{ + QObject* sdr = sender(); + Db* db = dynamic_cast(sdr); + if (!db) + { + qWarning() << "Received connected() signal but could not cast it to Db!"; + return; + } + emit dbConnected(db); +} + +void DbManagerImpl::dbDisconnectedSlot() +{ + QObject* sdr = sender(); + Db* db = dynamic_cast(sdr); + if (!db) + { + qWarning() << "Received disconnected() signal but could not cast it to Db!"; + return; + } + emit dbDisconnected(db); +} + +void DbManagerImpl::dbAboutToDisconnect(bool& deny) +{ + QObject* sdr = sender(); + Db* db = dynamic_cast(sdr); + if (!db) + { + qWarning() << "Received dbAboutToDisconnect() signal but could not cast it to Db!"; + return; + } + emit dbAboutToBeDisconnected(db, deny); +} + +void DbManagerImpl::aboutToUnload(Plugin* plugin, PluginType* type) +{ + if (!type->isForPluginType()) + return; + + InvalidDb* invalidDb = nullptr; + DbPlugin* dbPlugin = dynamic_cast(plugin); + QList toRemove; + for (Db* db : dbList) + { + if (!dbPlugin->checkIfDbServedByPlugin(db)) + continue; + + toRemove << db; + } + + for (Db* db : toRemove) + { + emit dbAboutToBeUnloaded(db, dbPlugin); + + if (db->isOpen()) + db->close(); + + removeDbInternal(db, false); + + invalidDb = new InvalidDb(db->getName(), db->getPath(), db->getConnectionOptions()); + invalidDb->setError(tr("No supporting plugin loaded.")); + addDbInternal(invalidDb, false); + + delete db; + + emit dbUnloaded(invalidDb); + } +} + +void DbManagerImpl::loaded(Plugin* plugin, PluginType* type) +{ + if (!type->isForPluginType()) + return; + + DbPlugin* dbPlugin = dynamic_cast(plugin); + Db* db = nullptr; + + QUrl url; + for (Db* invalidDb : getInvalidDatabases()) + { + if (invalidDb->getConnectionOptions().contains(DB_PLUGIN) && invalidDb->getConnectionOptions()[DB_PLUGIN].toString() != dbPlugin->getName()) + continue; + + url = QUrl::fromUserInput(invalidDb->getPath()); + if (url.isLocalFile() && !QFile::exists(invalidDb->getPath())) + continue; + + db = createDb(invalidDb->getName(), invalidDb->getPath(), invalidDb->getConnectionOptions()); + if (!db) + continue; // For this db driver was not loaded yet. + + if (!dbPlugin->checkIfDbServedByPlugin(db)) + { + qDebug() << "Managed to load database" << db->getPath() << " (" << db->getName() << ")" + << "but it doesn't use DbPlugin that was just loaded, so it will not be loaded to the db manager"; + + delete db; + continue; + } + + removeDbInternal(invalidDb, false); + delete invalidDb; + + addDbInternal(db, false); + + if (!db->getConnectionOptions().contains(DB_PLUGIN)) + { + db->getConnectionOptions()[DB_PLUGIN] = dbPlugin->getName(); + if (!CFG->updateDb(db->getName(), db->getName(), db->getPath(), db->getConnectionOptions())) + qWarning() << "Could not store handling plugin in options for database" << db->getName(); + } + + if (CFG->getDbGroup(db->getName())->open) + db->open(); + + emit dbLoaded(db); + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h new file mode 100644 index 0000000..8e28080 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h @@ -0,0 +1,183 @@ +#ifndef DBMANAGERIMPL_H +#define DBMANAGERIMPL_H + +#include "db/db.h" +#include "coreSQLiteStudio_global.h" +#include "common/strhash.h" +#include "common/global.h" +#include "services/dbmanager.h" +#include +#include +#include +#include +#include + +class InvalidDb; + +class API_EXPORT DbManagerImpl : public DbManager +{ + Q_OBJECT + + public: + /** + * @brief Creates database manager. + * @param parent Parent object passed to QObject constructor. + */ + explicit DbManagerImpl(QObject *parent = 0); + + /** + * @brief Default destructor. + */ + ~DbManagerImpl(); + + bool addDb(const QString &name, const QString &path, const QHash &options, bool permanent = true); + bool addDb(const QString &name, const QString &path, bool permanent = true); + bool updateDb(Db* db, const QString &name, const QString &path, const QHash &options, bool permanent); + void removeDbByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive); + void removeDbByPath(const QString& path); + void removeDb(Db* db); + QList getDbList(); + QList getValidDbList(); + QList getConnectedDbList(); + QStringList getDbNames(); + Db* getByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive); + Db* getByPath(const QString& path); + Db* createInMemDb(); + bool isTemporary(Db* db); + QString quickAddDb(const QString &path, const QHash &options); + + /** + * @brief Defines database plugin used for creating in-memory databases. + * @param plugin Plugin to use. + */ + void setInMemDbCreatorPlugin(DbPlugin* plugin); + + private: + /** + * @brief Internal manager initialization. + * + * Called from any constructor. + */ + void init(); + + /** + * @brief Loads initial list of databases. + * + * Loaded databases are initially the invalid databases. + * They are turned into valid databases once their plugins are loaded. + */ + void loadInitialDbList(); + + /** + * @brief Removes database from application. + * @param db Database to be removed. + * @param alsoFromConfig If true, database will also be removed from configuration file, otherwise it's just from the manager. + * + * This method is internally called by public methods, as they all do pretty much the same thing, + * except they accept different input parameter. Then this method does the actual job. + */ + void removeDbInternal(Db* db, bool alsoFromConfig = true); + + /** + * @brief Adds database to the application. + * @param db Database to be added. + * @param alsoToConfig If true, the database will also be added to configuration file, otherwise it will be onle to the manager. + * + * When addDb() is called, it calls DbPlugin#getInstance() and if it returns object, then this method + * is called to register the database object in dbList variable. + */ + void addDbInternal(Db* db, bool alsoToConfig = true); + + /** + * @brief Filters invalid databases from all managed databases. + * @return Only invalid databases from this manager. + */ + QList getInvalidDatabases() const; + + Db* tryToLoadDb(InvalidDb* invalidDb); + + /** + * @brief Creates database object. + * @param name Symbolic name of the database. + * @param path Database file path. + * @param options Database options, such as password, etc. + * @param errorMessages If not null, then the error messages from DbPlugins are stored in that string (in case this method returns null). + * @return Database object, or null pointer. + * + * This method is used internally by addDb() methods. It goes through all DbPlugin instances + * and checks if any of them supports given file path and options and returns a database object. + * First plugin that provides database object is accepted and its result is returned from the method. + */ + static Db* createDb(const QString &name, const QString &path, const QHash &options, QString* errorMessages = nullptr); + + /** + * @brief Registered databases list. Both permanent and transient databases. + */ + QList dbList; + + /** + * @brief Database ame to database instance mapping, with keys being case insensitive. + */ + StrHash nameToDb; + + /** + * @brief Mapping from file path to the database. + * + * Mapping from database file path (as passed to addDb() or updateDb()) to the actual database object. + */ + QHash pathToDb; + + /** + * @brief Lock for dbList. + * Lock for dbList, so the list can be accessed from multiple threads. + */ + QReadWriteLock listLock; + + /** + * @brief Database plugin used to create in-memory databases. + */ + DbPlugin* inMemDbCreatorPlugin = nullptr; + + private slots: + /** + * @brief Slot called when connected to db. + * + * The slot is connected to the database object, therefore the database object has to be extracted from signal sender + * and converted to database type, then passed to the dbConnected(Db* db) signal. + */ + void dbConnectedSlot(); + /** + * @brief Slot called when connected to db. + * + * The slot is connected to the database object, therefore the database object has to be extracted from signal sender + * and converted to database type, then passed to the dbConnected(Db* db) signal. + */ + void dbDisconnectedSlot(); + + /** + * @brief Passes Db::aboutToDisconnect() signal to dbAboutToBeDisconnected() signal. + */ + void dbAboutToDisconnect(bool& deny); + + /** + * @brief Removes databases handled by the plugin from the list. + * @param plugin DbPlugin (any other will be ignored). + * @param type DbPlugin type. + * It removes all databases handled by the plugin being unloaded from the list of managed databases. + */ + void aboutToUnload(Plugin* plugin, PluginType* type); + + /** + * @brief Adds all configured databases handled by the plugin to managed list. + * @param plugin DbPlugin (any other will be ignored). + * @param type DbPlugin type. + * Checks configuration for any databases managed by the plugin and if there is any, it's loaded into the managed list. + */ + void loaded(Plugin* plugin, PluginType* type); + + public slots: + void notifyDatabasesAreLoaded(); + void scanForNewDatabasesInConfig(); +}; + +#endif // DBMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp new file mode 100644 index 0000000..c94e4c2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp @@ -0,0 +1,694 @@ +#include "functionmanagerimpl.h" +#include "services/config.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" +#include "plugins/scriptingplugin.h" +#include "common/unused.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "services/dbmanager.h" +#include "db/queryexecutor.h" +#include "db/sqlquery.h" +#include +#include +#include +#include +#include +#include + +FunctionManagerImpl::FunctionManagerImpl() +{ + init(); +} + +void FunctionManagerImpl::setScriptFunctions(const QList& newFunctions) +{ + clearFunctions(); + functions = newFunctions; + refreshFunctionsByKey(); + storeInConfig(); + emit functionListChanged(); +} + +QList FunctionManagerImpl::getAllScriptFunctions() const +{ + return functions; +} + +QList FunctionManagerImpl::getScriptFunctionsForDatabase(const QString& dbName) const +{ + QList results; + foreach (ScriptFunction* func, functions) + { + if (func->allDatabases || func->databases.contains(dbName, Qt::CaseInsensitive)) + results << func; + } + return results; +} + +QVariant FunctionManagerImpl::evaluateScalar(const QString& name, int argCount, const QList& args, Db* db, bool& ok) +{ + Key key; + key.name = name; + key.argCount = argCount; + key.type = ScriptFunction::SCALAR; + if (functionsByKey.contains(key)) + { + ScriptFunction* function = functionsByKey[key]; + return evaluateScriptScalar(function, name, argCount, args, db, ok); + } + else if (nativeFunctionsByKey.contains(key)) + { + NativeFunction* function = nativeFunctionsByKey[key]; + return evaluateNativeScalar(function, args, db, ok); + } + + ok = false; + return cannotFindFunctionError(name, argCount); +} + +void FunctionManagerImpl::evaluateAggregateInitial(const QString& name, int argCount, Db* db, QHash& aggregateStorage) +{ + Key key; + key.name = name; + key.argCount = argCount; + key.type = ScriptFunction::AGGREGATE; + if (functionsByKey.contains(key)) + { + ScriptFunction* function = functionsByKey[key]; + evaluateScriptAggregateInitial(function, db, aggregateStorage); + } +} + +void FunctionManagerImpl::evaluateAggregateStep(const QString& name, int argCount, const QList& args, Db* db, QHash& aggregateStorage) +{ + Key key; + key.name = name; + key.argCount = argCount; + key.type = ScriptFunction::AGGREGATE; + if (functionsByKey.contains(key)) + { + ScriptFunction* function = functionsByKey[key]; + evaluateScriptAggregateStep(function, args, db, aggregateStorage); + } +} + +QVariant FunctionManagerImpl::evaluateAggregateFinal(const QString& name, int argCount, Db* db, bool& ok, QHash& aggregateStorage) +{ + Key key; + key.name = name; + key.argCount = argCount; + key.type = ScriptFunction::AGGREGATE; + if (functionsByKey.contains(key)) + { + ScriptFunction* function = functionsByKey[key]; + return evaluateScriptAggregateFinal(function, name, argCount, db, ok, aggregateStorage); + } + + ok = false; + return cannotFindFunctionError(name, argCount); +} + +QVariant FunctionManagerImpl::evaluateScriptScalar(ScriptFunction* func, const QString& name, int argCount, const QList& args, Db* db, bool& ok) +{ + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang); + if (!plugin) + { + ok = false; + return langUnsupportedError(name, argCount, func->lang); + } + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast(plugin); + + QString error; + QVariant result; + + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(func->code, args, db, false, &error); + else + result = plugin->evaluate(func->code, args, &error); + + if (!error.isEmpty()) + { + ok = false; + return error; + } + return result; +} + +void FunctionManagerImpl::evaluateScriptAggregateInitial(ScriptFunction* func, Db* db, QHash& aggregateStorage) +{ + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang); + if (!plugin) + return; + + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast(plugin); + + ScriptingPlugin::Context* ctx = plugin->createContext(); + aggregateStorage["context"] = QVariant::fromValue(ctx); + + if (dbAwarePlugin) + dbAwarePlugin->evaluate(ctx, func->code, {}, db, false); + else + plugin->evaluate(ctx, func->code, {}); + + if (plugin->hasError(ctx)) + { + aggregateStorage["error"] = true; + aggregateStorage["errorMessage"] = plugin->getErrorMessage(ctx); + } +} + +void FunctionManagerImpl::evaluateScriptAggregateStep(ScriptFunction* func, const QList& args, Db* db, QHash& aggregateStorage) +{ + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang); + if (!plugin) + return; + + if (aggregateStorage.contains("error")) + return; + + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast(plugin); + + ScriptingPlugin::Context* ctx = aggregateStorage["context"].value(); + if (dbAwarePlugin) + dbAwarePlugin->evaluate(ctx, func->code, args, db, false); + else + plugin->evaluate(ctx, func->code, args); + + if (plugin->hasError(ctx)) + { + aggregateStorage["error"] = true; + aggregateStorage["errorMessage"] = plugin->getErrorMessage(ctx); + } +} + +QVariant FunctionManagerImpl::evaluateScriptAggregateFinal(ScriptFunction* func, const QString& name, int argCount, Db* db, bool& ok, QHash& aggregateStorage) +{ + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang); + if (!plugin) + { + ok = false; + return langUnsupportedError(name, argCount, func->lang); + } + + ScriptingPlugin::Context* ctx = aggregateStorage["context"].value(); + if (aggregateStorage.contains("error")) + { + ok = false; + plugin->releaseContext(ctx); + return aggregateStorage["errorMessage"]; + } + + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast(plugin); + + QVariant result; + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(ctx, func->code, {}, db, false); + else + result = plugin->evaluate(ctx, func->code, {}); + + if (plugin->hasError(ctx)) + { + ok = false; + QString msg = plugin->getErrorMessage(ctx); + plugin->releaseContext(ctx); + return msg; + } + + plugin->releaseContext(ctx); + return result; +} + +QList FunctionManagerImpl::getAllNativeFunctions() const +{ + return nativeFunctions; +} + +QVariant FunctionManagerImpl::evaluateNativeScalar(NativeFunction* func, const QList& args, Db* db, bool& ok) +{ + if (!func->undefinedArgs && args.size() != func->arguments.size()) + { + ok = false; + return tr("Invalid number of arguments to function '%1'. Expected %2, but got %3.").arg(func->name, QString::number(func->arguments.size()), + QString::number(args.size())); + } + + return func->functionPtr(args, db, ok); +} + +void FunctionManagerImpl::init() +{ + loadFromConfig(); + initNativeFunctions(); + refreshFunctionsByKey(); +} + +void FunctionManagerImpl::initNativeFunctions() +{ + registerNativeFunction("regexp", {"pattern", "arg"}, FunctionManagerImpl::nativeRegExp); + registerNativeFunction("sqlfile", {"file"}, FunctionManagerImpl::nativeSqlFile); + registerNativeFunction("readfile", {"file"}, FunctionManagerImpl::nativeReadFile); + registerNativeFunction("writefile", {"file", "data"}, FunctionManagerImpl::nativeWriteFile); + registerNativeFunction("langs", {}, FunctionManagerImpl::nativeLangs); + registerNativeFunction("script", {"language", "code"}, FunctionManagerImpl::nativeScript); + registerNativeFunction("html_escape", {"string"}, FunctionManagerImpl::nativeHtmlEscape); + registerNativeFunction("url_encode", {"string"}, FunctionManagerImpl::nativeUrlEncode); + registerNativeFunction("url_decode", {"string"}, FunctionManagerImpl::nativeUrlDecode); + registerNativeFunction("base64_encode", {"data"}, FunctionManagerImpl::nativeBase64Encode); + registerNativeFunction("base64_decode", {"data"}, FunctionManagerImpl::nativeBase64Decode); + registerNativeFunction("md4_bin", {"data"}, FunctionManagerImpl::nativeMd4); + registerNativeFunction("md4", {"data"}, FunctionManagerImpl::nativeMd4Hex); + registerNativeFunction("md5_bin", {"data"}, FunctionManagerImpl::nativeMd5); + registerNativeFunction("md5", {"data"}, FunctionManagerImpl::nativeMd5Hex); + registerNativeFunction("sha1", {"data"}, FunctionManagerImpl::nativeSha1); + registerNativeFunction("sha224", {"data"}, FunctionManagerImpl::nativeSha224); + registerNativeFunction("sha256", {"data"}, FunctionManagerImpl::nativeSha256); + registerNativeFunction("sha384", {"data"}, FunctionManagerImpl::nativeSha384); + registerNativeFunction("sha512", {"data"}, FunctionManagerImpl::nativeSha512); + registerNativeFunction("sha3_224", {"data"}, FunctionManagerImpl::nativeSha3_224); + registerNativeFunction("sha3_256", {"data"}, FunctionManagerImpl::nativeSha3_256); + registerNativeFunction("sha3_384", {"data"}, FunctionManagerImpl::nativeSha3_384); + registerNativeFunction("sha3_512", {"data"}, FunctionManagerImpl::nativeSha3_512); +} + +void FunctionManagerImpl::refreshFunctionsByKey() +{ + functionsByKey.clear(); + foreach (ScriptFunction* func, functions) + functionsByKey[Key(func)] = func; + + foreach (NativeFunction* func, nativeFunctions) + nativeFunctionsByKey[Key(func)] = func; +} + +void FunctionManagerImpl::storeInConfig() +{ + QVariantList list; + QHash fnHash; + foreach (ScriptFunction* func, functions) + { + fnHash["name"] = func->name; + fnHash["lang"] = func->lang; + fnHash["code"] = func->code; + fnHash["initCode"] = func->initCode; + fnHash["finalCode"] = func->finalCode; + fnHash["databases"] = common(DBLIST->getDbNames(), func->databases); + fnHash["arguments"] = func->arguments; + fnHash["type"] = static_cast(func->type); + fnHash["undefinedArgs"] = func->undefinedArgs; + fnHash["allDatabases"] = func->allDatabases; + list << fnHash; + } + CFG_CORE.Internal.Functions.set(list); +} + +void FunctionManagerImpl::loadFromConfig() +{ + clearFunctions(); + + QVariantList list = CFG_CORE.Internal.Functions.get(); + QHash fnHash; + ScriptFunction* func = nullptr; + for (const QVariant& var : list) + { + fnHash = var.toHash(); + func = new ScriptFunction(); + func->name = fnHash["name"].toString(); + func->lang = fnHash["lang"].toString(); + func->code = fnHash["code"].toString(); + func->initCode = fnHash["initCode"].toString(); + func->finalCode = fnHash["finalCode"].toString(); + func->databases = fnHash["databases"].toStringList(); + func->arguments = fnHash["arguments"].toStringList(); + func->type = static_cast(fnHash["type"].toInt()); + func->undefinedArgs = fnHash["undefinedArgs"].toBool(); + func->allDatabases = fnHash["allDatabases"].toBool(); + functions << func; + } +} + +void FunctionManagerImpl::clearFunctions() +{ + for (ScriptFunction* fn : functions) + delete fn; + + functions.clear(); +} + +QString FunctionManagerImpl::cannotFindFunctionError(const QString& name, int argCount) +{ + QStringList argMarkers = getArgMarkers(argCount); + return tr("No such function registered in SQLiteStudio: %1(%2)").arg(name).arg(argMarkers.join(",")); +} + +QString FunctionManagerImpl::langUnsupportedError(const QString& name, int argCount, const QString& lang) +{ + QStringList argMarkers = getArgMarkers(argCount); + return tr("Function %1(%2) was registered with language %3, but the plugin supporting that language is not currently loaded.") + .arg(name).arg(argMarkers.join(",")).arg(lang); +} + +QVariant FunctionManagerImpl::nativeRegExp(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 2) + { + ok = false; + return QVariant(); + } + + QRegularExpression re(args[0].toString()); + if (!re.isValid()) + { + ok = false; + return tr("Invalid regular expression pattern: %1").arg(args[0].toString()); + } + + QRegularExpressionMatch match = re.match(args[1].toString()); + return match.hasMatch(); +} + +QVariant FunctionManagerImpl::nativeSqlFile(const QList& args, Db* db, bool& ok) +{ + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + QFile file(args[0].toString()); + if (!file.open(QIODevice::ReadOnly)) + { + ok = false; + return tr("Could not open file %1 for reading: %2").arg(args[0].toString(), file.errorString()); + } + + QTextStream stream(&file); + QString sql = stream.readAll(); + file.close(); + + QueryExecutor executor(db); + executor.setAsyncMode(false); + executor.exec(sql); + SqlQueryPtr results = executor.getResults(); + if (results->isError()) + { + ok = false; + return results->getErrorText(); + } + return results->getSingleCell(); +} + +QVariant FunctionManagerImpl::nativeReadFile(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + QFile file(args[0].toString()); + if (!file.open(QIODevice::ReadOnly)) + { + ok = false; + return tr("Could not open file %1 for reading: %2").arg(args[0].toString(), file.errorString()); + } + + QByteArray data = file.readAll(); + file.close(); + return data; +} + +QVariant FunctionManagerImpl::nativeWriteFile(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 2) + { + ok = false; + return QVariant(); + } + + QFile file(args[0].toString()); + if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) + { + ok = false; + return tr("Could not open file %1 for writting: %2").arg(args[0].toString(), file.errorString()); + } + + QByteArray data; + switch (args[1].type()) + { + case QVariant::String: + data = args[1].toString().toLocal8Bit(); + break; + default: + data = args[1].toByteArray(); + break; + } + + int res = file.write(data); + file.close(); + + if (res < 0) + { + ok = false; + return tr("Error while writting to file %1: %2").arg(args[0].toString(), file.errorString()); + } + + return res; +} + +QVariant FunctionManagerImpl::nativeScript(const QList& args, Db* db, bool& ok) +{ + if (args.size() != 2) + { + ok = false; + return QVariant(); + } + + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(args[0].toString()); + if (!plugin) + { + ok = false; + return tr("Unsupported scripting language: %1").arg(args[0].toString()); + } + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast(plugin); + + QString error; + QVariant result; + + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(args[1].toString(), QList(), db, false, &error); + else + result = plugin->evaluate(args[1].toString(), QList(), &error); + + if (!error.isEmpty()) + { + ok = false; + return error; + } + return result; +} + +QVariant FunctionManagerImpl::nativeLangs(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 0) + { + ok = false; + return QVariant(); + } + + QStringList names; + for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins()) + names << plugin->getLanguage(); + + return names.join(", "); +} + +QVariant FunctionManagerImpl::nativeHtmlEscape(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return args[0].toString().toHtmlEscaped(); +} + +QVariant FunctionManagerImpl::nativeUrlEncode(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return QUrl::toPercentEncoding(args[0].toString()); +} + +QVariant FunctionManagerImpl::nativeUrlDecode(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return QUrl::fromPercentEncoding(args[0].toString().toLocal8Bit()); +} + +QVariant FunctionManagerImpl::nativeBase64Encode(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return args[0].toByteArray().toBase64(); +} + +QVariant FunctionManagerImpl::nativeBase64Decode(const QList& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return QByteArray::fromBase64(args[0].toByteArray()); +} + +QVariant FunctionManagerImpl::nativeCryptographicFunction(const QList& args, Db* db, bool& ok, QCryptographicHash::Algorithm algo) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return QCryptographicHash::hash(args[0].toByteArray(), algo); +} + +QVariant FunctionManagerImpl::nativeMd4(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md4); +} + +QVariant FunctionManagerImpl::nativeMd4Hex(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md4).toByteArray().toHex(); +} + +QVariant FunctionManagerImpl::nativeMd5(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md5); +} + +QVariant FunctionManagerImpl::nativeMd5Hex(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md5).toByteArray().toHex(); +} + +QVariant FunctionManagerImpl::nativeSha1(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha1); +} + +QVariant FunctionManagerImpl::nativeSha224(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha224); +} + +QVariant FunctionManagerImpl::nativeSha256(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha256); +} + +QVariant FunctionManagerImpl::nativeSha384(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha384); +} + +QVariant FunctionManagerImpl::nativeSha512(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha512); +} + +QVariant FunctionManagerImpl::nativeSha3_224(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_224); +} + +QVariant FunctionManagerImpl::nativeSha3_256(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_256); +} + +QVariant FunctionManagerImpl::nativeSha3_384(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_384); +} + +QVariant FunctionManagerImpl::nativeSha3_512(const QList& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_512); +} + +QStringList FunctionManagerImpl::getArgMarkers(int argCount) +{ + QStringList argMarkers; + for (int i = 0; i < argCount; i++) + argMarkers << "?"; + + return argMarkers; +} + +void FunctionManagerImpl::registerNativeFunction(const QString& name, const QStringList& args, FunctionManager::NativeFunction::ImplementationFunction funcPtr) +{ + NativeFunction* nf = new NativeFunction(); + nf->name = name; + nf->arguments = args; + nf->type = FunctionBase::SCALAR; + nf->undefinedArgs = false; + nf->functionPtr = funcPtr; + nativeFunctions << nf; +} + +int qHash(const FunctionManagerImpl::Key& key) +{ + return qHash(key.name) ^ key.argCount ^ static_cast(key.type); +} + +bool operator==(const FunctionManagerImpl::Key& key1, const FunctionManagerImpl::Key& key2) +{ + return key1.name == key2.name && key1.type == key2.type && key1.argCount == key2.argCount; +} + +FunctionManagerImpl::Key::Key() +{ +} + +FunctionManagerImpl::Key::Key(FunctionBase* function) : + name(function->name), argCount(function->undefinedArgs ? -1 : function->arguments.size()), type(function->type) +{ +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.h new file mode 100644 index 0000000..d8734e6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.h @@ -0,0 +1,96 @@ +#ifndef FUNCTIONMANAGERIMPL_H +#define FUNCTIONMANAGERIMPL_H + +#include "services/functionmanager.h" +#include + +class SqlFunctionPlugin; +class Plugin; +class PluginType; + +class API_EXPORT FunctionManagerImpl : public FunctionManager +{ + Q_OBJECT + + public: + FunctionManagerImpl(); + + void setScriptFunctions(const QList& newFunctions); + QList getAllScriptFunctions() const; + QList getScriptFunctionsForDatabase(const QString& dbName) const; + QList getAllNativeFunctions() const; + QVariant evaluateScalar(const QString& name, int argCount, const QList& args, Db* db, bool& ok); + void evaluateAggregateInitial(const QString& name, int argCount, Db* db, QHash& aggregateStorage); + void evaluateAggregateStep(const QString& name, int argCount, const QList& args, Db* db, QHash& aggregateStorage); + QVariant evaluateAggregateFinal(const QString& name, int argCount, Db* db, bool& ok, QHash& aggregateStorage); + QVariant evaluateScriptScalar(ScriptFunction* func, const QString& name, int argCount, const QList& args, Db* db, bool& ok); + void evaluateScriptAggregateInitial(ScriptFunction* func, Db* db, + QHash& aggregateStorage); + void evaluateScriptAggregateStep(ScriptFunction* func, const QList& args, Db* db, + QHash& aggregateStorage); + QVariant evaluateScriptAggregateFinal(ScriptFunction* func, const QString& name, int argCount, Db* db, bool& ok, + QHash& aggregateStorage); + QVariant evaluateNativeScalar(NativeFunction* func, const QList& args, Db* db, bool& ok); + + private: + struct Key + { + Key(); + Key(FunctionBase* function); + + QString name; + int argCount; + FunctionBase::Type type; + }; + + friend int qHash(const FunctionManagerImpl::Key& key); + friend bool operator==(const FunctionManagerImpl::Key& key1, const FunctionManagerImpl::Key& key2); + + void init(); + void initNativeFunctions(); + void refreshFunctionsByKey(); + void refreshNativeFunctionsByKey(); + void storeInConfig(); + void loadFromConfig(); + void clearFunctions(); + QString cannotFindFunctionError(const QString& name, int argCount); + QString langUnsupportedError(const QString& name, int argCount, const QString& lang); + void registerNativeFunction(const QString& name, const QStringList& args, NativeFunction::ImplementationFunction funcPtr); + + static QStringList getArgMarkers(int argCount); + static QVariant nativeRegExp(const QList& args, Db* db, bool& ok); + static QVariant nativeSqlFile(const QList& args, Db* db, bool& ok); + static QVariant nativeReadFile(const QList& args, Db* db, bool& ok); + static QVariant nativeWriteFile(const QList& args, Db* db, bool& ok); + static QVariant nativeScript(const QList& args, Db* db, bool& ok); + static QVariant nativeLangs(const QList& args, Db* db, bool& ok); + static QVariant nativeHtmlEscape(const QList& args, Db* db, bool& ok); + static QVariant nativeUrlEncode(const QList& args, Db* db, bool& ok); + static QVariant nativeUrlDecode(const QList& args, Db* db, bool& ok); + static QVariant nativeBase64Encode(const QList& args, Db* db, bool& ok); + static QVariant nativeBase64Decode(const QList& args, Db* db, bool& ok); + static QVariant nativeCryptographicFunction(const QList& args, Db* db, bool& ok, QCryptographicHash::Algorithm algo); + static QVariant nativeMd4(const QList& args, Db* db, bool& ok); + static QVariant nativeMd4Hex(const QList& args, Db* db, bool& ok); + static QVariant nativeMd5(const QList& args, Db* db, bool& ok); + static QVariant nativeMd5Hex(const QList& args, Db* db, bool& ok); + static QVariant nativeSha1(const QList& args, Db* db, bool& ok); + static QVariant nativeSha224(const QList& args, Db* db, bool& ok); + static QVariant nativeSha256(const QList& args, Db* db, bool& ok); + static QVariant nativeSha384(const QList& args, Db* db, bool& ok); + static QVariant nativeSha512(const QList& args, Db* db, bool& ok); + static QVariant nativeSha3_224(const QList& args, Db* db, bool& ok); + static QVariant nativeSha3_256(const QList& args, Db* db, bool& ok); + static QVariant nativeSha3_384(const QList& args, Db* db, bool& ok); + static QVariant nativeSha3_512(const QList& args, Db* db, bool& ok); + + QList functions; + QHash functionsByKey; + QList nativeFunctions; + QHash nativeFunctionsByKey; +}; + +int qHash(const FunctionManagerImpl::Key& key); +bool operator==(const FunctionManagerImpl::Key& key1, const FunctionManagerImpl::Key& key2); + +#endif // FUNCTIONMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp new file mode 100644 index 0000000..5d7a517 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp @@ -0,0 +1,820 @@ +#include "pluginmanagerimpl.h" +#include "plugins/scriptingplugin.h" +#include "plugins/genericplugin.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include +#include +#include +#include +#include + +PluginManagerImpl::PluginManagerImpl() +{ +} + +PluginManagerImpl::~PluginManagerImpl() +{ +} + +void PluginManagerImpl::init() +{ + pluginDirs += qApp->applicationDirPath() + "/plugins"; + pluginDirs += QDir(CFG->getConfigDir()).absoluteFilePath("plugins"); + + QString envDirs = SQLITESTUDIO->getEnv("SQLITESTUDIO_PLUGINS"); + if (!envDirs.isNull()) + pluginDirs += envDirs.split(PATH_LIST_SEPARATOR); + +#ifdef PLUGINS_DIR + pluginDirs += STRINGIFY(PLUGINS_DIR); +#endif + +#ifdef SYS_PLUGINS_DIR + pluginDirs += STRINGIFY(SYS_PLUGINS_DIR); +#endif + +#ifdef Q_OS_MACX + pluginDirs += QCoreApplication::applicationDirPath()+"/../PlugIns"; +#endif + + scanPlugins(); + loadPlugins(); +} + +void PluginManagerImpl::deinit() +{ + emit aboutToQuit(); + + // Plugin containers and their plugins + foreach (PluginContainer* container, pluginContainer.values()) + { + if (container->builtIn) + { + container->plugin->deinit(); + delete container->plugin; + } + else + unload(container->name); + } + + foreach (PluginContainer* container, pluginContainer.values()) + delete container; + + pluginContainer.clear(); + + // Types + foreach (PluginType* type, registeredPluginTypes) + delete type; + + registeredPluginTypes.clear(); + pluginCategories.clear(); +} + +QList PluginManagerImpl::getPluginTypes() const +{ + return registeredPluginTypes; +} + +QStringList PluginManagerImpl::getPluginDirs() const +{ + return pluginDirs; +} + +QString PluginManagerImpl::getFilePath(Plugin* plugin) const +{ + if (!pluginContainer.contains(plugin->getName())) + return QString::null; + + return pluginContainer[plugin->getName()]->filePath; +} + +bool PluginManagerImpl::loadBuiltInPlugin(Plugin* plugin) +{ + bool res = initPlugin(plugin); + res &= plugin->init(); + return res; +} + +PluginType* PluginManagerImpl::getPluginType(Plugin* plugin) const +{ + if (!pluginContainer.contains(plugin->getName())) + return nullptr; + + return pluginContainer[plugin->getName()]->type; +} + +void PluginManagerImpl::scanPlugins() +{ + QStringList nameFilters; + nameFilters << "*.so" << "*.dll" << "*.dylib"; + + QPluginLoader* loader = nullptr; + foreach (QString pluginDirPath, pluginDirs) + { + QDir pluginDir(pluginDirPath); + foreach (QString fileName, pluginDir.entryList(nameFilters, QDir::Files)) + { + fileName = pluginDir.absoluteFilePath(fileName); + loader = new QPluginLoader(fileName); + loader->setLoadHints(QLibrary::ExportExternalSymbolsHint|QLibrary::ResolveAllSymbolsHint); + + if (!initPlugin(loader, fileName)) + { + qDebug() << "File" << fileName << "was loaded as plugin, but SQLiteStudio couldn't initialize plugin."; + delete loader; + } + } + } + + QStringList names; + for (PluginContainer* container : pluginContainer.values()) + { + if (!container->builtIn) + names << container->name; + } + + qDebug() << "Following plugins found:" << names; +} + +void PluginManagerImpl::loadPlugins() +{ + QStringList alreadyAttempted; + for (const QString& pluginName : pluginContainer.keys()) + { + if (shouldAutoLoad(pluginName)) + load(pluginName, alreadyAttempted); + } + + pluginsAreInitiallyLoaded = true; + emit pluginsInitiallyLoaded(); +} + +bool PluginManagerImpl::initPlugin(QPluginLoader* loader, const QString& fileName) +{ + QJsonObject pluginMetaData = loader->metaData(); + QString pluginTypeName = pluginMetaData.value("MetaData").toObject().value("type").toString(); + PluginType* pluginType = nullptr; + foreach (PluginType* type, registeredPluginTypes) + { + if (type->getName() == pluginTypeName) + { + pluginType = type; + break; + } + } + + if (!pluginType) + { + qWarning() << "Could not load plugin" + fileName + "because its type was not recognized:" << pluginTypeName; + return false; + } + + QString pluginName = pluginMetaData.value("className").toString(); + QJsonObject metaObject = pluginMetaData.value("MetaData").toObject(); + + if (!checkPluginRequirements(pluginName, metaObject)) + return false; + + PluginContainer* container = new PluginContainer; + container->type = pluginType; + container->filePath = fileName; + container->loaded = false; + container->loader = loader; + pluginCategories[pluginType] << container; + pluginContainer[pluginName] = container; + + if (!readDependencies(pluginName, container, metaObject.value("dependencies"))) + return false; + + if (!readConflicts(pluginName, container, metaObject.value("conflicts"))) + return false; + + if (!readMetaData(container)) + { + delete container; + return false; + } + + return true; +} + +bool PluginManagerImpl::checkPluginRequirements(const QString& pluginName, const QJsonObject& metaObject) +{ + if (metaObject.value("gui").toBool(false) && !SQLITESTUDIO->isGuiAvailable()) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires GUI and this is not GUI client running."; + return false; + } + + int minVer = metaObject.value("minQtVersion").toInt(0); + if (QT_VERSION_CHECK(minVer / 10000, minVer / 100 % 100, minVer % 10000) > QT_VERSION) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires at least Qt version" << toPrintableVersion(minVer) << ", but got" << QT_VERSION_STR; + return false; + } + + int maxVer = metaObject.value("maxQtVersion").toInt(999999); + if (QT_VERSION_CHECK(maxVer / 10000, maxVer / 100 % 100, maxVer % 10000) < QT_VERSION) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires at most Qt version" << toPrintableVersion(maxVer) << ", but got" << QT_VERSION_STR; + return false; + } + + minVer = metaObject.value("minAppVersion").toInt(0); + if (SQLITESTUDIO->getVersion() < minVer) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires at least SQLiteStudio version" << toPrintableVersion(minVer) << ", but got" + << SQLITESTUDIO->getVersionString(); + return false; + } + + maxVer = metaObject.value("maxAppVersion").toInt(999999); + if (SQLITESTUDIO->getVersion() > maxVer) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires at most SQLiteStudio version" << toPrintableVersion(maxVer) << ", but got" + << SQLITESTUDIO->getVersionString(); + return false; + } + + return true; +} + +bool PluginManagerImpl::readDependencies(const QString& pluginName, PluginManagerImpl::PluginContainer* container, const QJsonValue& depsValue) +{ + if (depsValue.isUndefined()) + return true; + + QJsonArray depsArray; + if (depsValue.type() == QJsonValue::Array) + depsArray = depsValue.toArray(); + else + depsArray.append(depsValue); + + PluginDependency dep; + QJsonObject depObject; + for (const QJsonValue& value : depsArray) + { + if (value.type() == QJsonValue::Object) + { + depObject = value.toObject(); + if (!depObject.contains("name")) + { + qWarning() << "Invalid dependency entry in plugin" << pluginName << " - doesn't contain 'name' of the dependency."; + return false; + } + + dep.name = depObject.value("name").toString(); + dep.minVersion = depObject.value("minVersion").toInt(0); + dep.maxVersion = depObject.value("maxVersion").toInt(0); + } + else + { + dep.maxVersion = 0; + dep.minVersion = 0; + dep.name = value.toString(); + } + container->dependencies << dep; + } + return true; +} + +bool PluginManagerImpl::readConflicts(const QString& pluginName, PluginManagerImpl::PluginContainer* container, const QJsonValue& confValue) +{ + UNUSED(pluginName); + + if (confValue.isUndefined()) + return true; + + QJsonArray confArray; + if (confValue.type() == QJsonValue::Array) + confArray = confValue.toArray(); + else + confArray.append(confValue); + + for (const QJsonValue& value : confArray) + container->conflicts << value.toString(); + + return true; +} + +bool PluginManagerImpl::initPlugin(Plugin* plugin) +{ + QString pluginName = plugin->getName(); + PluginType* pluginType = nullptr; + foreach (PluginType* type, registeredPluginTypes) + { + if (type->test(plugin)) + { + pluginType = type; + break; + } + } + + if (!pluginType) + { + qWarning() << "Could not load built-in plugin" + pluginName + "because its type was not recognized."; + return false; + } + + PluginContainer* container = new PluginContainer; + container->type = pluginType; + container->loaded = true; + container->builtIn = true; + container->plugin = plugin; + pluginCategories[pluginType] << container; + pluginContainer[pluginName] = container; + if (!readMetaData(container)) + { + delete container; + return false; + } + + pluginLoaded(container); + return true; +} + +bool PluginManagerImpl::shouldAutoLoad(const QString& pluginName) +{ + QStringList loadedPlugins = CFG_CORE.General.LoadedPlugins.get().split(",", QString::SkipEmptyParts); + QStringList pair; + foreach (const QString& loadedPlugin, loadedPlugins) + { + pair = loadedPlugin.split("="); + if (pair.size() != 2) + { + qWarning() << "Invalid entry in config General.LoadedPlugins:" << loadedPlugin; + continue; + } + + if (pair[0] == pluginName) + return (bool)pair[1].toInt(); + } + + return true; +} + +QStringList PluginManagerImpl::getAllPluginNames(PluginType* type) const +{ + QStringList names; + if (!pluginCategories.contains(type)) + return names; + + foreach (PluginContainer* container, pluginCategories[type]) + names << container->name; + + return names; +} + +QStringList PluginManagerImpl::getAllPluginNames() const +{ + return pluginContainer.keys(); +} + +PluginType* PluginManagerImpl::getPluginType(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return nullptr; + + return pluginContainer[pluginName]->type; +} + +QString PluginManagerImpl::getAuthor(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QString::null; + + return pluginContainer[pluginName]->author; +} + +QString PluginManagerImpl::getTitle(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QString::null; + + return pluginContainer[pluginName]->title; +} + +QString PluginManagerImpl::getPrintableVersion(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QString::null; + + return pluginContainer[pluginName]->printableVersion; +} + +int PluginManagerImpl::getVersion(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return 0; + + return pluginContainer[pluginName]->version; +} + +QString PluginManagerImpl::getDescription(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QString::null; + + return pluginContainer[pluginName]->description; +} + +void PluginManagerImpl::unload(Plugin* plugin) +{ + if (!plugin) + return; + + unload(plugin->getName()); +} + +void PluginManagerImpl::unload(const QString& pluginName) +{ + if (!pluginContainer.contains(pluginName)) + { + qWarning() << "No such plugin in containers:" << pluginName << "while trying to unload plugin."; + return; + } + + // Checking preconditions + PluginContainer* container = pluginContainer[pluginName]; + if (container->builtIn) + return; + + if (!container->loaded) + return; + + // Unloading depdendent plugins + for (PluginContainer* otherContainer : pluginContainer.values()) + { + if (otherContainer == container) + continue; + + for (const PluginDependency& dep : otherContainer->dependencies) + { + if (dep.name == pluginName) + { + unload(otherContainer->name); + break; + } + } + } + + // Removing from fast-lookup collections + removePluginFromCollections(container->plugin); + + // Deinitializing and unloading plugin + emit aboutToUnload(container->plugin, container->type); + container->plugin->deinit(); + + QPluginLoader* loader = container->loader; + if (!loader->isLoaded()) + { + qWarning() << "QPluginLoader says the plugin is not loaded. Weird."; + emit unloaded(container->name, container->type); + return; + } + + loader->unload(); + + container->plugin = nullptr; + container->loaded = false; + + emit unloaded(container->name, container->type); + + qDebug() << pluginName << "unloaded:" << container->filePath; +} + +bool PluginManagerImpl::load(const QString& pluginName) +{ + QStringList alreadyAttempted; + bool res = load(pluginName, alreadyAttempted); + if (!res) + emit failedToLoad(pluginName); + + return res; +} + +bool PluginManagerImpl::load(const QString& pluginName, QStringList& alreadyAttempted, int minVersion, int maxVersion) +{ + if (alreadyAttempted.contains(pluginName)) + return false; + + // Checking initial conditions + if (!pluginContainer.contains(pluginName)) + { + qWarning() << "No such plugin in containers:" << pluginName << "while trying to load plugin."; + alreadyAttempted.append(pluginName); + return false; + } + + PluginContainer* container = pluginContainer[pluginName]; + + if (minVersion > 0 && container->version < minVersion) + { + qWarning() << "Requested plugin" << pluginName << "in version at least" << minVersion << "but have:" << container->version; + return false; + } + + if (maxVersion > 0 && container->version > maxVersion) + { + qWarning() << "Requested plugin" << pluginName << "in version at most" << maxVersion << "but have:" << container->version; + return false; + } + + if (container->builtIn) + return true; + + QPluginLoader* loader = container->loader; + if (loader->isLoaded()) + return true; + + // Checking for conflicting plugins + for (PluginContainer* otherContainer : pluginContainer.values()) + { + if (!otherContainer->loaded || otherContainer->name == pluginName) + continue; + + if (container->conflicts.contains(otherContainer->name) || otherContainer->conflicts.contains(pluginName)) + { + notifyWarn(tr("Cannot load plugin %1, because it's in conflict with plugin %2.").arg(pluginName, otherContainer->name)); + alreadyAttempted.append(pluginName); + return false; + } + } + + // Loading depended plugins + for (const PluginDependency& dep : container->dependencies) + { + if (!load(dep.name, alreadyAttempted, dep.minVersion, dep.maxVersion)) + { + notifyWarn(tr("Cannot load plugin %1, because its dependency was not loaded: %2.").arg(pluginName, dep.name)); + alreadyAttempted.append(pluginName); + return false; + } + } + + // Loading pluginName + if (!loader->load()) + { + notifyWarn(tr("Cannot load plugin %1. Error details: %2").arg(pluginName, loader->errorString())); + alreadyAttempted.append(pluginName); + return false; + } + + // Initializing loaded plugin + Plugin* plugin = dynamic_cast(container->loader->instance()); + GenericPlugin* genericPlugin = dynamic_cast(plugin); + if (genericPlugin) + { + genericPlugin->loadMetaData(container->loader->metaData()); + } + + if (!plugin->init()) + { + loader->unload(); + notifyWarn(tr("Cannot load plugin %1 (error while initializing plugin).").arg(pluginName)); + alreadyAttempted.append(pluginName); + return false; + } + + pluginLoaded(container); + + return true; +} + +void PluginManagerImpl::pluginLoaded(PluginManagerImpl::PluginContainer* container) +{ + if (!container->builtIn) + { + container->plugin = dynamic_cast(container->loader->instance()); + container->loaded = true; + } + addPluginToCollections(container->plugin); + + emit loaded(container->plugin, container->type); + if (!container->builtIn) + qDebug() << container->name << "loaded:" << container->filePath; +} + +void PluginManagerImpl::addPluginToCollections(Plugin* plugin) +{ + ScriptingPlugin* scriptingPlugin = dynamic_cast(plugin); + if (scriptingPlugin) + scriptingPlugins[scriptingPlugin->getLanguage()] = scriptingPlugin; +} + +void PluginManagerImpl::removePluginFromCollections(Plugin* plugin) +{ + ScriptingPlugin* scriptingPlugin = dynamic_cast(plugin); + if (scriptingPlugin && scriptingPlugins.contains(scriptingPlugin->getLanguage())) + scriptingPlugins.remove(plugin->getName()); +} + +bool PluginManagerImpl::readMetaData(PluginManagerImpl::PluginContainer* container) +{ + if (container->loader) + { + QHash metaData = readMetaData(container->loader->metaData()); + container->name = metaData["name"].toString(); + container->version = metaData["version"].toInt(); + container->printableVersion = toPrintableVersion(metaData["version"].toInt()); + container->author = metaData["author"].toString(); + container->description = metaData["description"].toString(); + container->title = metaData["title"].toString(); + } + else if (container->plugin) + { + container->name = container->plugin->getName(); + container->version = container->plugin->getVersion(); + container->printableVersion = container->plugin->getPrintableVersion(); + container->author = container->plugin->getAuthor(); + container->description = container->plugin->getDescription(); + container->title = container->plugin->getTitle(); + } + else + { + qCritical() << "Could not read metadata for some plugin. It has no loader or plugin object defined."; + return false; + } + return true; +} + +bool PluginManagerImpl::isLoaded(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + { + qWarning() << "No such plugin in containers:" << pluginName << "while trying to get plugin 'loaded' status."; + return false; + } + + return pluginContainer[pluginName]->loaded; +} + +bool PluginManagerImpl::isBuiltIn(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + { + qWarning() << "No such plugin in containers:" << pluginName << "while trying to get plugin 'builtIn' status."; + return false; + } + + return pluginContainer[pluginName]->builtIn; +} + +Plugin* PluginManagerImpl::getLoadedPlugin(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return nullptr; + + if (!pluginContainer[pluginName]->loaded) + return nullptr; + + return pluginContainer[pluginName]->plugin; +} + +QList PluginManagerImpl::getLoadedPlugins(PluginType* type) const +{ + QList list; + if (!pluginCategories.contains(type)) + return list; + + foreach (PluginContainer* container, pluginCategories[type]) + { + if (container->loaded) + list << container->plugin; + } + + return list; +} + +ScriptingPlugin* PluginManagerImpl::getScriptingPlugin(const QString& languageName) const +{ + if (scriptingPlugins.contains(languageName)) + return scriptingPlugins[languageName]; + + return nullptr; +} + +QHash PluginManagerImpl::readMetaData(const QJsonObject& metaData) +{ + QHash results; + results["name"] = metaData.value("className").toString(); + + QJsonObject root = metaData.value("MetaData").toObject(); + results["type"] = root.value("type").toString(); + results["title"] = root.value("title").toString(); + results["description"] = root.value("description").toString(); + results["author"] = root.value("author").toString(); + results["version"] = root.value("version").toInt(); + results["ui"] = root.value("ui").toString(); + return results; +} + +QString PluginManagerImpl::toPrintableVersion(int version) const +{ + static const QString versionStr = QStringLiteral("%1.%2.%3"); + return versionStr.arg(version / 10000) + .arg(version / 100 % 100) + .arg(version % 100); +} + +QStringList PluginManagerImpl::getDependencies(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QStringList(); + + static const QString verTpl = QStringLiteral(" (%1)"); + QString minVerTpl = tr("min: %1", "plugin dependency version"); + QString maxVerTpl = tr("max: %1", "plugin dependency version"); + QStringList outputList; + QString depStr; + QStringList depVerList; + for (const PluginDependency& dep : pluginContainer[pluginName]->dependencies) + { + depStr = dep.name; + if (dep.minVersion > 0 || dep.maxVersion > 0) + { + depVerList.clear(); + if (dep.minVersion > 0) + depVerList << minVerTpl.arg(toPrintableVersion(dep.minVersion)); + + if (dep.maxVersion > 0) + depVerList << minVerTpl.arg(toPrintableVersion(dep.maxVersion)); + + depStr += verTpl.arg(depVerList.join(", ")); + } + outputList << depStr; + } + + return outputList; +} + +QStringList PluginManagerImpl::getConflicts(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QStringList(); + + return pluginContainer[pluginName]->conflicts; +} + +bool PluginManagerImpl::arePluginsInitiallyLoaded() const +{ + return pluginsAreInitiallyLoaded; +} + +QList PluginManagerImpl::getLoadedPlugins() const +{ + QList plugins; + foreach (PluginContainer* container, pluginContainer.values()) + { + if (container->loaded) + plugins << container->plugin; + } + return plugins; +} + +QStringList PluginManagerImpl::getLoadedPluginNames() const +{ + QStringList names; + foreach (PluginContainer* container, pluginContainer.values()) + { + if (container->loaded) + names << container->name; + } + return names; +} + +QList PluginManagerImpl::getAllPluginDetails() const +{ + QList results; + PluginManager::PluginDetails details; + foreach (PluginContainer* container, pluginContainer.values()) + { + details.name = container->name; + details.title = container->title; + details.description = container->description; + details.builtIn = container->builtIn; + details.version = container->version; + details.filePath = container->filePath; + details.versionString = formatVersion(container->version); + results << details; + } + return results; +} + +QList PluginManagerImpl::getLoadedPluginDetails() const +{ + QList results = getAllPluginDetails(); + QMutableListIterator it(results); + while (it.hasNext()) + { + if (!isLoaded(it.next().name)) + it.remove(); + } + return results; +} + +void PluginManagerImpl::registerPluginType(PluginType* type) +{ + registeredPluginTypes << type; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.h new file mode 100644 index 0000000..6968cab --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.h @@ -0,0 +1,321 @@ +#ifndef PLUGINMANAGERIMPL_H +#define PLUGINMANAGERIMPL_H + +#include "services/pluginmanager.h" +#include +#include + +class API_EXPORT PluginManagerImpl : public PluginManager +{ + Q_OBJECT + + public: + /** + * @brief Creates plugin manager. + */ + PluginManagerImpl(); + + /** + * @brief Deletes plugin manager. + */ + ~PluginManagerImpl(); + + void init(); + void deinit(); + QList getPluginTypes() const; + QStringList getPluginDirs() const; + QString getFilePath(Plugin* plugin) const; + bool loadBuiltInPlugin(Plugin* plugin); + bool load(const QString& pluginName); + void unload(const QString& pluginName); + void unload(Plugin* plugin); + bool isLoaded(const QString& pluginName) const; + bool isBuiltIn(const QString& pluginName) const; + Plugin* getLoadedPlugin(const QString& pluginName) const; + QStringList getAllPluginNames(PluginType* type) const; + QStringList getAllPluginNames() const; + PluginType* getPluginType(const QString& pluginName) const; + QString getAuthor(const QString& pluginName) const; + QString getTitle(const QString& pluginName) const; + QString getPrintableVersion(const QString& pluginName) const; + int getVersion(const QString& pluginName) const; + QString getDescription(const QString& pluginName) const; + PluginType* getPluginType(Plugin* plugin) const; + QList getLoadedPlugins(PluginType* type) const; + ScriptingPlugin* getScriptingPlugin(const QString& languageName) const; + QHash readMetaData(const QJsonObject& metaData); + QString toPrintableVersion(int version) const; + QStringList getDependencies(const QString& pluginName) const; + QStringList getConflicts(const QString& pluginName) const; + bool arePluginsInitiallyLoaded() const; + QList getLoadedPlugins() const; + QStringList getLoadedPluginNames() const; + QList getAllPluginDetails() const; + QList getLoadedPluginDetails() const; + + protected: + void registerPluginType(PluginType* type); + + private: + struct PluginDependency + { + QString name; + int minVersion = 0; + int maxVersion = 0; + }; + + /** + * @brief Container for plugin related data. + * + * The container is used to represent plugin available to the application, + * no matter if it's loaded or not. It keeps all plugin related data, + * so it's available even the plugin is not loaded. + */ + struct PluginContainer + { + /** + * @brief Name of the plugin. + */ + QString name; + + /** + * @brief Title of the plugin, used on UI. + */ + QString title; + + /** + * @brief Plugin's detailed description. + */ + QString description; + + /** + * @brief Plugin's author. + */ + QString author; + + /** + * @brief Numeric verion of the plugin. + */ + int version; + + /** + * @brief Human-readable version. + */ + QString printableVersion; + + /** + * @brief Type of the plugin. + */ + PluginType* type = nullptr; + + /** + * @brief Full path to the plugin's file. + */ + QString filePath; + + /** + * @brief Plugin's loaded state flag. + */ + bool loaded; + + /** + * @brief Qt's plugin framework loaded for this plugin. + */ + QPluginLoader* loader = nullptr; + + /** + * @brief Plugin object. + * + * It's null when plugin is not loaded. + */ + Plugin* plugin = nullptr; + + /** + * @brief Flag indicating that the plugin is built in. + * + * Plugins built-in are classes implementing plugin's interface, + * but they are compiled and statically linked to the main application binary. + * They cannot be loaded or unloaded - they are loaded by default. + */ + bool builtIn = false; + + /** + * @brief Names of plugnis that this plugin depends on. + */ + QList dependencies; + + /** + * @brief Names of plugins that this plugin conflicts with. + */ + QStringList conflicts; + }; + + /** + * @brief List of plugins, both loaded and unloaded. + */ + typedef QList PluginContainerList; + + /** + * @brief Scans plugin directories to find out available plugins. + * + * It looks in the following locations: + *
    + *
  • application_directory/plugins/ + *
  • application_config_directory/plugins/ + *
  • directory pointed by the SQLITESTUDIO_PLUGINS environment variable + *
  • directory compiled in as PLUGINS_DIR parameter of the compilation + *
+ * + * The application_directory is a directory where the application executable is. + * The application_config_directory can be different, see ConfigImpl::initDbFile() for details. + * The SQLITESTUDIO_PLUGINS variable can contain several paths, separated by : (for Unix/Mac) or ; (for Windows). + */ + void scanPlugins(); + + /** + * @brief Loads plugins defined in configuration. + * + * It loads all plugins that are available to the application + * and are not marked to not load in the configuration. + * + * In other words, every plugin will load by default, unless it was + * explicitly unloaded previously and that was saved in the configuration + * (when application was closing). + */ + void loadPlugins(); + + /** + * @brief Loads given plugin. + * @param pluginName Name of the plugin to load. + * @param alreadyAttempted List of plugin names that were already attempted to be loaded. + * @param minVersion Minimum required version of the plugin to load. + * @param maxVersion Maximum required version of the plugin to load. + * @return true on success, false on failure. + * + * This is pretty much what the public load() method does, except this one tracks what plugins were already + * attempted to be loaded (and failed), so it doesn't warn twice about the same plugin if it failed + * to load while it was a dependency for some other plugins. + * + * It also allows to define minimum and maximum plugin version, so if SQLiteStudio has the plugin available, + * but the version is out of required range, it will also fail to load. + */ + bool load(const QString& pluginName, QStringList& alreadyAttempted, int minVersion = 0, int maxVersion = 0); + + /** + * @brief Executes standard routines after plugin was loaded. + * @param container Container for the loaded plugin. + * + * It fills all members of the plugin container and emits loaded() signal. + */ + void pluginLoaded(PluginContainer* container); + + /** + * @brief Stores some specific plugin types in internal collections for faster access. + * @param plugin Plugin that was just loaded. + * + * This is called after we are sure we have a Plugin instance. + * + * The method stores certain plugin types in internal collections, so they can be accessed + * faster, instead of calling getLoadedPlugin(), which is not as fast. + * + * The internal collections are used for plugins that are likely to be accessed frequently, + * like ScriptingPlugin. + */ + void addPluginToCollections(Plugin* plugin); + + /** + * @brief Removes plugin from internal collections. + * @param plugin Plugin that is about to be unloaded. + * + * This is the reverse operation to what addPluginToCollections(Plugin*) does. + */ + void removePluginFromCollections(Plugin* plugin); + + /** + * @brief Reads title, description, author, etc. from the plugin. + * @param plugin Plugin to read data from. + * @param container Container to put the data to. + * @return true on success, false on problems (with details in logs) + * + * It does the reading by calling all related methods from Plugin interface, + * then stores those information in given \p container. + * + * The built-in plugins define those methods using their class metadata. + * + * External plugins provide this information in their file metadata + * and this method uses QPluginLoader to read this metadata. + */ + bool readMetaData(PluginContainer* container); + + /** + * @brief Creates plugin container and initializes it. + * @param loader Qt's plugin framework loader used to load this plugin. + * For built-in plugins (statically linked) this must be null. + * @param fileName Plugin's file path. For built-in plugins it's ignored. + * @param plugin Plugin object from loaded plugin. + * @return true if the initialization succeeded, or false otherwise. + * + * It assigns plugin type to the plugin, creates plugin container and fills + * all necessary data for the plugin. If the plugin was configured to not load, + * then this method unloads the file, before plugin was initialized (with Plugin::init()). + * + * All plugins are loaded at the start, but before they are fully initialized + * and enabled, they are simply queried for metadata, then either unloaded + * (when configured to not load at startup), or the initialization proceeds. + */ + bool initPlugin(QPluginLoader* loader, const QString& fileName); + + bool checkPluginRequirements(const QString& pluginName, const QJsonObject& metaObject); + bool readDependencies(const QString& pluginName, PluginContainer* container, const QJsonValue& depsValue); + bool readConflicts(const QString& pluginName, PluginContainer* container, const QJsonValue& confValue); + + /** + * @brief Creates plugin container and initializes it. + * @param plugin Built-in plugin object. + * @return true if the initialization succeeded, or false otherwise. + * + * This is pretty much the same as the other initPlugin() method, but this one is for built-in plugins. + */ + bool initPlugin(Plugin* plugin); + + /** + * @brief Tests if given plugin is configured to be loaded at startup. + * @param plugin Tested plugin object. + * @return true if plugin should be loaded at startup, or false otherwise. + * + * This method checks General.LoadedPlugins configuration entry to see if plugin + * was explicitly disabled for loading at startup. + */ + bool shouldAutoLoad(const QString& pluginName); + + /** + * @brief List of plugin directories (not necessarily absolute paths). + */ + QStringList pluginDirs; + + /** + * @brief List of registered plugin types. + */ + QList registeredPluginTypes; + + /** + * @brief Table with plugin types as keys and list of plugins assigned for each type. + */ + QHash pluginCategories; + + /** + * @brief Table with plugin names and containers assigned for each plugin. + */ + QHash pluginContainer; + + /** + * @brief Internal list of scripting plugins, updated on load/unload of plugins. + * + * Keys are scripting language name. It's a separate table to optimize querying scripting plugins. + */ + QHash scriptingPlugins; + + bool pluginsAreInitiallyLoaded = false; +}; + +#endif // PLUGINMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/importmanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/importmanager.cpp new file mode 100644 index 0000000..53803e5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/importmanager.cpp @@ -0,0 +1,104 @@ +#include "importmanager.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" +#include "plugins/importplugin.h" +#include "importworker.h" +#include "db/db.h" +#include "common/unused.h" +#include +#include + +ImportManager::ImportManager() +{ +} + +QStringList ImportManager::getImportDataSourceTypes() const +{ + QStringList types; + for (ImportPlugin* plugin : PLUGINS->getLoadedPlugins()) + types << plugin->getDataSourceTypeName(); + + return types; +} + +ImportPlugin* ImportManager::getPluginForDataSourceType(const QString& dataSourceType) const +{ + for (ImportPlugin* plugin : PLUGINS->getLoadedPlugins()) + { + if (plugin->getDataSourceTypeName() == dataSourceType) + return plugin; + } + + return nullptr; +} + +void ImportManager::configure(const QString& dataSourceType, const ImportManager::StandardImportConfig& config) +{ + plugin = getPluginForDataSourceType(dataSourceType); + importConfig = config; +} + +void ImportManager::importToTable(Db* db, const QString& table) +{ + this->db = db; + this->table = table; + + if (importInProgress) + { + emit importFailed(); + qCritical() << "Tried to import while other import was in progress."; + return; + } + + if (!db->isOpen()) + { + emit importFailed(); + qCritical() << "Tried to import into closed database."; + return; + } + + if (!plugin) + { + emit importFailed(); + qCritical() << "Tried to import, while ImportPlugin was null."; + return; + } + + importInProgress = true; + + ImportWorker* worker = new ImportWorker(plugin, &importConfig, db, table); + connect(worker, SIGNAL(finished(bool)), this, SLOT(finalizeImport(bool))); + connect(worker, SIGNAL(createdTable(Db*,QString)), this, SLOT(handleTableCreated(Db*,QString))); + connect(this, SIGNAL(orderWorkerToInterrupt()), worker, SLOT(interrupt())); + + QThreadPool::globalInstance()->start(worker); +} + +void ImportManager::interrupt() +{ + emit orderWorkerToInterrupt(); +} + +bool ImportManager::isAnyPluginAvailable() +{ + return PLUGINS->getLoadedPlugins().size() > 0; +} + +void ImportManager::finalizeImport(bool result) +{ + importInProgress = false; + emit importFinished(); + if (result) + { + notifyInfo(tr("Imported data to the table '%1' successfully.").arg(table)); + emit importSuccessful(); + } + else + emit importFailed(); +} + +void ImportManager::handleTableCreated(Db* db, const QString& table) +{ + UNUSED(table); + emit schemaModified(db); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/importmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/importmanager.h new file mode 100644 index 0000000..6f13826 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/importmanager.h @@ -0,0 +1,85 @@ +#ifndef IMPORTMANAGER_H +#define IMPORTMANAGER_H + +#include "pluginservicebase.h" +#include "coreSQLiteStudio_global.h" +#include +#include + +class ImportPlugin; +class Db; +class CfgEntry; + +class API_EXPORT ImportManager : public PluginServiceBase +{ + Q_OBJECT + + public: + struct StandardImportConfig + { + /** + * @brief Text encoding. + * + * Always one of QTextCodec::availableCodecs(). + * Codec is important for text-based data. For binary data it should irrelevant to the import plugin. + */ + QString codec; + + /** + * @brief Name of the file that the import is being done from. + * + * This is provided just for information to the import process, + * but the plugin should use data stream provided to each called import method, + * instead of opening the file from this name. + * + * It will be null string if importing is not performed from a file, but from somewhere else + * (for example from a clipboard). + */ + QString inputFileName; + }; + + enum StandardConfigFlag + { + CODEC = 0x01, /**< Text encoding (see StandardImportConfig::codec). */ + FILE_NAME = 0x02, /**< Input file (see StandardImportConfig::inputFileName). */ + }; + + Q_DECLARE_FLAGS(StandardConfigFlags, StandardConfigFlag) + + ImportManager(); + + QStringList getImportDataSourceTypes() const; + ImportPlugin* getPluginForDataSourceType(const QString& dataSourceType) const; + + void configure(const QString& dataSourceType, const StandardImportConfig& config); + void importToTable(Db* db, const QString& table); + + static bool isAnyPluginAvailable(); + + private: + StandardImportConfig importConfig; + ImportPlugin* plugin = nullptr; + bool importInProgress = false; + Db* db = nullptr; + QString table; + + public slots: + void interrupt(); + + private slots: + void finalizeImport(bool result); + void handleTableCreated(Db* db, const QString& table); + + signals: + void importFinished(); + void importSuccessful(); + void importFailed(); + void orderWorkerToInterrupt(); + void schemaModified(Db* db); +}; + +#define IMPORT_MANAGER SQLITESTUDIO->getImportManager() + +Q_DECLARE_OPERATORS_FOR_FLAGS(ImportManager::StandardConfigFlags) + +#endif // IMPORTMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.cpp new file mode 100644 index 0000000..0980399 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.cpp @@ -0,0 +1,85 @@ +#include "services/notifymanager.h" + +DEFINE_SINGLETON(NotifyManager) + +NotifyManager::NotifyManager(QObject *parent) : + QObject(parent) +{ +} + +void NotifyManager::error(const QString &msg) +{ + addToRecentList(recentErrors, msg); + emit notifyError(msg); +} + +void NotifyManager::warn(const QString &msg) +{ + addToRecentList(recentWarnings, msg); + emit notifyWarning(msg); +} + +void NotifyManager::info(const QString &msg) +{ + addToRecentList(recentInfos, msg); + emit notifyInfo(msg); +} + +void NotifyManager::modified(Db* db, const QString& database, const QString& object) +{ + emit objectModified(db, database, object); +} + +void NotifyManager::deleted(Db* db, const QString& database, const QString& object) +{ + emit objectDeleted(db, database, object); +} + +void NotifyManager::createded(Db* db, const QString& database, const QString& object) +{ + emit objectCreated(db, database, object); +} + +void NotifyManager::renamed(Db* db, const QString& database, const QString& oldObject, const QString& newObject) +{ + emit objectRenamed(db, database, oldObject, newObject); +} + +void NotifyManager::addToRecentList(QStringList& list, const QString &message) +{ + list << message; + if (list.size() <= maxRecentMessages) + return; + + list = list.mid(list.length() - maxRecentMessages); +} + +QList NotifyManager::getRecentInfos() const +{ + return recentInfos; +} + +QList NotifyManager::getRecentWarnings() const +{ + return recentWarnings; +} + +QList NotifyManager::getRecentErrors() const +{ + return recentErrors; +} + +void notifyError(const QString &msg) +{ + NotifyManager::getInstance()->error(msg); +} + +void notifyWarn(const QString &msg) +{ + NotifyManager::getInstance()->warn(msg); +} + +void notifyInfo(const QString &msg) +{ + NotifyManager::getInstance()->info(msg); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.h b/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.h new file mode 100644 index 0000000..5bb4571 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.h @@ -0,0 +1,58 @@ +#ifndef NOTIFYMANAGER_H +#define NOTIFYMANAGER_H + +#include "db/db.h" +#include "common/global.h" +#include +#include + +class API_EXPORT NotifyManager : public QObject +{ + Q_OBJECT + + DECLARE_SINGLETON(NotifyManager) + + public: + explicit NotifyManager(QObject *parent = 0); + + QList getRecentErrors() const; + QList getRecentWarnings() const; + QList getRecentInfos() const; + + signals: + void notifyError(const QString& msg); + void notifyWarning(const QString& msg); + void notifyInfo(const QString& msg); + + void objectModified(Db* db, const QString& database, const QString& object); + void objectDeleted(Db* db, const QString& database, const QString& object); + void objectCreated(Db* db, const QString& database, const QString& object); + void objectRenamed(Db* db, const QString& database, const QString& oldObject, const QString& newObject); + + public slots: + void error(const QString& msg); + void warn(const QString& msg); + void info(const QString& msg); + + void modified(Db* db, const QString& database, const QString& object); + void deleted(Db* db, const QString& database, const QString& object); + void createded(Db* db, const QString& database, const QString& object); + void renamed(Db* db, const QString& database, const QString& oldObject, const QString& newObject); + + private: + void addToRecentList(QStringList& list, const QString& message); + + static const constexpr int maxRecentMessages = 10; + + QStringList recentErrors; + QStringList recentWarnings; + QStringList recentInfos; +}; + +#define NOTIFY_MANAGER NotifyManager::getInstance() + +void API_EXPORT notifyError(const QString& msg); +void API_EXPORT notifyWarn(const QString& msg); +void API_EXPORT notifyInfo(const QString& msg); + +#endif // NOTIFYMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h new file mode 100644 index 0000000..4f822bc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h @@ -0,0 +1,528 @@ +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "plugins/plugin.h" +#include "plugins/plugintype.h" +#include "common/global.h" +#include "sqlitestudio.h" +#include + +class Plugin; +class ScriptingPlugin; + +/** @file */ + +/** + * @brief The plugin manager. + * + * It's a singleton accessible with PLUGINS macro. + * + * It provides methods to load, unload and query plugins. It stores loaded + * plugins in configuration on application close and loads that plugins during next startup. + * If there's a plugin which was not defined if it was loaded or not - it is loaded by default. + * + * Description of Plugin interface contains list of directories scanned for plugins. + * + * There's a macro for global access to the PluginManager - ::PLUGINS. It actually calls + * SQLiteStudio::getInstance() and from there it calls SQLiteStudio::getPluginManager(). + * + * Plugins in PluginManager are organized by types. The manager has a list of types and for each type + * there's a list of plugins of that type. Plugin types are represented by PluginType class. + * + * @section querying_plugins Querying available and loaded plugins + * + * To query all plugins available to the application (including those not loaded) use getAllPluginNames(). + * + * To query if certain plugin is loaded use isLoaded(). + * + * To query all plugins loaded to the application use getLoadedPlugins(). It requires either PluginType, + * or plugin interface class (for template method version) to determinate what group of plugins you're + * interested in. To return all plugins (no matter what type), use template method version with Plugin + * as an interface type for parameter. An example of getting all SQL formatter plugins: + * @code + * QList formatterPlugins = PLUGINS->getLoadedPlugins(); + * @endcode + * + * To get list of plugin types use getPluginTypes(). + * + * To get PluginType for given plugin interface use getPluginType(). + * + * These are just the most important methods to query plugins. See full list of methods for more. + * + * @section load_unload Loading and unloading plugins + * + * To load plugin use load(). + * + * To unload plugin use unload(). + * + * Apart from that, all plugins are loaded initially (unless they were unloaded last time during + * application close). + * + * @section plugin_types Specialized plugin types + * + * Each plugin must implement Plugin interface, but it also can implement other interfaces, + * which makes them suitable for fulfilling certain functionalities. For example all plugins + * implementing SqlFormatterPlugin will automatically be available to SqlFormatter object, + * because PluginManager knows which plugins implement SqlFormatterPlugin and can provide full + * list of those plugins to SqlFormatter. This is done by call to registerPluginType(). + * + * The registerPluginType() registers new type of plugins that will be recognizable by PluginManager. + * Once the new interface is registered with this method, all plugins will be tested against + * implementation for that type and those which implement the interface will be stored + * in the proper collection assigned for that plugin type. + * + * This way PluginManager can provide list of all plugins implementing given interface + * with getLoadedPlugins(). + * + * All registered plugin types can be queries by getPluginTypes() method. + */ +class API_EXPORT PluginManager : public QObject +{ + Q_OBJECT + + public: + struct PluginDetails + { + QString name; + QString title; + QString description; + bool builtIn = false; + int version = 0; + QString versionString; + QString filePath; + }; + + /** + * @brief Loads all plugins. + * + * Scans all plugin directories and tries to load all plugins found there. For list of directories + * see description of Plugin class. + */ + virtual void init() = 0; + + /** + * @brief Unloads all loaded plugins. + * + * Also deregisters all plugin types. + */ + virtual void deinit() = 0; + + /** + * @brief Provides list of registered plugin types. + * @return List of registered plugin types. + */ + virtual QList getPluginTypes() const = 0; + + /** + * @brief Provides list of plugin directories. + * @return List of directory paths (not necessarily absolute paths). + */ + virtual QStringList getPluginDirs() const = 0; + + /** + * @brief Provides absolute path to the plugin's file. + * @param plugin Loaded plugin. + * @return Absolute path to the plugin file. + */ + virtual QString getFilePath(Plugin* plugin) const = 0; + + /** + * @brief Loads instance of built-in plugin into the manager. + * @param plugin Plugin instance. + * @return true on success or false on failure (plugin's type could not be matched to registered plugin types). + * + * Built-in plugins are classes that implement plugin interface, but they are not in separate library. + * Instead they are classes compiled and linked to the main application. Such classes should be instantiated + * and passed to this method, so the PluginManager can treat it as any other plugin. + * + * @note Built-in plugins cannot be loaded or unloaded, so calls to load() or unload() will make no effect. + */ + virtual bool loadBuiltInPlugin(Plugin* plugin) = 0; + + /** + * @brief Loads the plugin. + * @param pluginName Name of the plugin to load. + * @return true on success, or false on failure. + * + * When loading a plugin, PluginManager loads the plugin file and resolves all its symbols inside. + * If that failed, file gets unloaded and the method returns false. + * + * Qt plugins framework will require that the loaded plugin will provide exactly one Plugin interface + * implementation. Otherwise file will be unloaded and this method will return false. + * + * Then the Plugin::init() method is called. It it returns false, then plugin is unloaded + * and this method returns false. + * + * Then meta information is read from the plugin (title, version, author, etc) - see Plugin for details. + * + * Then loaded plugin passes several tests against all registered plugin types. If it implements + * any type, it's added to the plugin list of that type. + * + * Then the loaded() signal is emitted. Finally, the true value is returned. + */ + virtual bool load(const QString& pluginName) = 0; + + /** + * @brief Unloads plugin. + * @param pluginName Plugin name to be unloaded. + * + * If the plugin is not loaded, this method does nothing. + * First the aboutToUnload() signal is emitted. Then Plugin::deinit() is called. + * Then the plugin library is unloaded (which causes Qt's plugins framework to delete the object + * implementing Plugin interface before the actual unloading). + * + * Finally, the unloaded() signal is emitted. + */ + virtual void unload(const QString& pluginName) = 0; + + /** + * @brief Unloads plugin. + * @param plugin Loaded plugin to be unloaded. + * @overload + */ + virtual void unload(Plugin* plugin) = 0; + + /** + * @brief Tests if given plugin is loaded. + * @param pluginName Name of the plugin to test. + * @return true if the plugin is loaded, or false otherwise. + */ + virtual bool isLoaded(const QString& pluginName) const = 0; + + /** + * @brief Tests whether given plugin is one of built-in plugins. + * @param pluginName Name of the plugin to test. + * @return true if the plugin is the built-in one, or false otherwise. + * + * @see loadBuiltInPlugin() + */ + virtual bool isBuiltIn(const QString& pluginName) const = 0; + + /** + * @brief Finds loaded plugin by name. + * @param pluginName Plugin name to look for. + * @return Loaded plugin object, or null of the plugin is not loaded. + */ + virtual Plugin* getLoadedPlugin(const QString& pluginName) const = 0; + + /** + * @brief Provides list of plugin names of given type. + * @param type Type of plugins to get names for. + * @return List of names. + * + * It returns names for all plugins available for the application, + * no matter they're currently loaded or not. + */ + virtual QStringList getAllPluginNames(PluginType* type) const = 0; + + virtual QList getAllPluginDetails() const = 0; + virtual QList getLoadedPluginDetails() const = 0; + + /** + * @brief Provides list of all plugin names. + * @return All available plugin names, no matter if loaded or not. + */ + virtual QStringList getAllPluginNames() const = 0; + + /** + * @brief Finds plugin's type. + * @param pluginName Plugin name (can be unloaded plugin). + * @return Type of the plugin, or null if plugin was not found by the name. + */ + virtual PluginType* getPluginType(const QString& pluginName) const = 0; + + /** + * @brief Provides plugin's author. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Author string defined in the plugin. + */ + virtual QString getAuthor(const QString& pluginName) const = 0; + + /** + * @brief Provides plugin's title. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Title string defined in the plugin. + */ + virtual QString getTitle(const QString& pluginName) const = 0; + + /** + * @brief Provides human-readable version of the plugin. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Version string defined in the plugin. + */ + virtual QString getPrintableVersion(const QString& pluginName) const = 0; + + /** + * @brief Provides numeric version of the plugin. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Numeric version defined in the plugin. + */ + virtual int getVersion(const QString& pluginName) const = 0; + + /** + * @brief Provides detailed description about the plugin. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Description defined in the plugin. + */ + virtual QString getDescription(const QString& pluginName) const = 0; + + /** + * @brief Tells plugin's type. + * @param plugin Loaded plugin. + * @return Type of the plugin. + */ + virtual PluginType* getPluginType(Plugin* plugin) const = 0; + + /** + * @brief Provides list of plugins for given plugin type. + * @param type Type of plugins. + * @return List of plugins for given type. + * + * This version of the method takes plugin type object as an discriminator. + * This way you can iterate through all types (using getPluginTypes()) + * and then for each type get list of plugins for that type, using this method. + */ + virtual QList getLoadedPlugins(PluginType* type) const = 0; + + /** + * @brief Provides list of all loaded plugins. + * @return List of plugins. + */ + virtual QList getLoadedPlugins() const = 0; + + /** + * @brief Provides names of all loaded plugins. + * @return List of plugin names. + */ + virtual QStringList getLoadedPluginNames() const = 0; + + /** + * @brief Provides scripting plugin for given scripting language if available. + * @param languageName Scripting language name to get plugin for. + * @return Plugin object or null if proper plugin was not found. + * + * Calling this function is similar in results to call to getLoadedPlugins() + * and then extracting a single plugin with desired scripting language support, except + * calling this function is much faster. PluginManager keeps scripting language plugins + * internally in hash table with language names as keys, so getting scripting plugin + * for desired language is way faster when using this method. + */ + virtual ScriptingPlugin* getScriptingPlugin(const QString& languageName) const = 0; + + /** + * @brief Loads metadata from given Json object. + * @param The metadata from json file. + * @return Metadata with keys: type, name, title, description, version, author, ui (optional). + */ + virtual QHash readMetaData(const QJsonObject& metaData) = 0; + + /** + * @brief Converts integer version to string version. + * @param version Integer version in XXYYZZ standard (see Plugin::getVersion() for details). + * @return Printable version string. + */ + virtual QString toPrintableVersion(int version) const = 0; + + /** + * @brief Provides list of plugin names that the queried plugin depends on. + * @param pluginName Queried plugin name. + * @return List of plugin names, usually an empty list. + * + * This is the list that is declared in plugins metadata under the "dependencies" key. + * The plugin can be loaded only if all its dependencies were successfully loaded. + */ + virtual QStringList getDependencies(const QString& pluginName) const = 0; + + /** + * @brief Provides list of plugin names that are declared to be in conflict with queries plugin. + * @param pluginName Queried plugin name, + * @return List of plugin names, usually an empty list. + * + * If a plugin declares other plugin (by name) to be its conflict (a "conflicts" key in plugin's metadata), + * then those 2 plugins cannot be loaded at the same time. SQLiteStudio will always refuse to load + * the other one, if the first one is already loaded - and vice versa. + * + * Declaring conflicts for a plugin can be useful for example if somebody wants to proivde an alternative + * implementation of SQLite2 database plugin, etc. In that case SQLiteStudio won't get confused in + * deciding which plugin to use for supporting such databases. + */ + virtual QStringList getConflicts(const QString& pluginName) const = 0; + + /** + * @brief Tells if plugins were already loaded on startup, or is this yet to happen. + * @return true if plugins were loaded, false if they are going to be loaded. + */ + virtual bool arePluginsInitiallyLoaded() const = 0; + + /** + * @brief registerPluginType Registers plugin type for loading and managing. + * @tparam T Interface class (as defined by Qt plugins standard) + * @param form Optional name of form object. + * @param title Optional title for configuration dialog. + * The form object name is different if you register new type by general type plugin. + * Built-in types are defined as the name of page from ConfigDialog. + * Types registered from plugins should use top widget name defined in the ui file. + * The title parameter is required if the configuration form was defined outside (in plugin). + * Title will be used for configuration dialog to display plugin type category (on the left of the dialog). + */ + template + void registerPluginType(const QString& title, const QString& form = QString()) + { + registerPluginType(new DefinedPluginType(title, form)); + } + + /** + * @brief Gets plugin type for given plugin interface. + * @tparam T Interface class of the plugin. + * @return Type of the plugin for given interface if registered, or null otherwise. + */ + template + PluginType* getPluginType() const + { + foreach (PluginType* type, getPluginTypes()) + { + if (!dynamic_cast*>(type)) + continue; + + return type; + } + return nullptr; + } + + /** + * @brief Provide list of plugins of given type. + * @tparam T Interface class of plugins, that we want to get. + * + * This method version gets plugin interface type as template parameter, + * so it returns list of loaded plugins that are already casted to requested + * interface type. + */ + template + QList getLoadedPlugins() const + { + QList typedPlugins; + PluginType* type = getPluginType(); + if (!type) + return typedPlugins; + + foreach (Plugin* plugin, getLoadedPlugins(type)) + typedPlugins << dynamic_cast(plugin); + + return typedPlugins; + } + + /** + * @brief Provide list of plugin names of given type. + * @tparam T Interface class of plugins, that we want to get names for. + * + * This method version gets plugin interface type as template parameter, + * so it returns list of names of loaded plugins. + */ + template + QStringList getLoadedPluginNames() const + { + QStringList names; + PluginType* type = getPluginType(); + if (!type) + return names; + + foreach (Plugin* plugin, getLoadedPlugins(type)) + names << plugin->getName(); + + return names; + } + + protected: + /** + * @brief Adds given type to registered plugins list. + * @param type Type instance. + * + * This is a helper method for registerPluginType() template function. + * The implementation should register given plugin type, that is - add it to a list of registered types. + */ + virtual void registerPluginType(PluginType* type) = 0; + + signals: + /** + * @brief Emitted just before plugin is unloaded. + * @param plugin Plugin object to be unloaded. + * @param type Type of the plugin. + * + * It's emitted just before call to Plugin::deinit(), destroying plugin object + * and unloading the plugin file. + * + * Any code using certain plugin should listen for this signal and stop using + * the plugin immediately when received this signal. Otherwise application may crash. + */ + void aboutToUnload(Plugin* plugin, PluginType* type); + + /** + * @brief Emitted just after plugin was loaded. + * @param plugin Plugin object from loaded plugin. + * @param type Plugin type. + * + * It's emitted after plugin was loaded and successfully initialized (which includes + * successful Plugin::init() call). + */ + void loaded(Plugin* plugin, PluginType* type); + + /** + * @brief Emitted after plugin was unloaded. + * @param pluginName Name of the plugin that was unloaded. + * @param type Type of the plugin. + * + * Emitted after plugin was deinitialized and unloaded. At this stage a plugin object + * is no longer available, only it's name and other metadata (like description, version, etc). + */ + void unloaded(const QString& pluginName, PluginType* type); + + /** + * @brief Emitted after initial plugin set was loaded. + * + * The initial load is performed at application startup. Any code that relies on + * some plugins being loaded (like for example code that loads list of databases relies on + * database support plugins) should listen to this signal. + */ + void pluginsInitiallyLoaded(); + + /** + * @brief Emitted when the plugin manager is deinitializing and will unload all plugins in a moment. + * + * It's emitted when user closes application, so the plugin manager deinitializes and unloads all plugins. + * This signal is emitted just before plugins get unloaded. + * If some signal handler is not interested in mass plugin unloading, then it can handle this signal + * and disconnect from unloaded() signal. + */ + void aboutToQuit(); + + /** + * @brief Emitted when plugin load was requested, but it failed. + * @param pluginName Name of the plugin that failed to load. + * + * It's used for example by ConfigDialog to uncheck plugin that was requested to load (checked) and it failed. + */ + void failedToLoad(const QString& pluginName); +}; + +/** + * @def PLUGINS + * @brief PluginsManager instance access macro. + * + * Since SQLiteStudio creates only one instance of PluginsManager, + * there is a standard method for accessing it, using code: + * @code + * QList types = SQLiteStudio::getInstance()->getPluginManager()->getPluginTypes(); + * @endcode + * or there's a slightly simpler way: + * @code + * QList types = SQLITESTUDIO->getPluginManager()->getPluginTypes(); + * @endcode + * or there is a very simplified method, using this macro: + * @code + * QList types = PLUGINS->getPluginTypes(); + * @endcode + */ +#define PLUGINS SQLITESTUDIO->getPluginManager() + +#endif // PLUGINMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp new file mode 100644 index 0000000..237667d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp @@ -0,0 +1,93 @@ +#include "populatemanager.h" +#include "services/pluginmanager.h" +#include "plugins/populateplugin.h" +#include "services/notifymanager.h" +#include "populateworker.h" +#include "plugins/populatesequence.h" +#include "plugins/populaterandom.h" +#include "plugins/populaterandomtext.h" +#include "plugins/populateconstant.h" +#include "plugins/populatedictionary.h" +#include "plugins/populatescript.h" +#include +#include + +PopulateManager::PopulateManager(QObject *parent) : + PluginServiceBase(parent) +{ + PLUGINS->loadBuiltInPlugin(new PopulateSequence()); + PLUGINS->loadBuiltInPlugin(new PopulateRandom()); + PLUGINS->loadBuiltInPlugin(new PopulateRandomText()); + PLUGINS->loadBuiltInPlugin(new PopulateConstant()); + PLUGINS->loadBuiltInPlugin(new PopulateDictionary()); + PLUGINS->loadBuiltInPlugin(new PopulateScript()); +} + +void PopulateManager::populate(Db* db, const QString& table, const QHash& engines, qint64 rows) +{ + if (workInProgress) + { + error(); + qCritical() << "Tried to call second populating process at the same time."; + return; + } + + if (!db->isOpen()) + { + error(); + qCritical() << "Tried to populate table in closed database."; + return; + } + + workInProgress = true; + + columns.clear(); + engineList.clear(); + for (const QString& column : engines.keys()) + { + columns << column; + engineList << engines[column]; + } + + + this->db = db; + this->table = table; + + PopulateWorker* worker = new PopulateWorker(db, table, columns, engineList, rows); + connect(worker, SIGNAL(finished(bool)), this, SLOT(finalizePopulating(bool))); + connect(this, SIGNAL(orderWorkerToInterrupt()), worker, SLOT(interrupt())); + + QThreadPool::globalInstance()->start(worker); + +} + +void PopulateManager::error() +{ + emit populatingFinished(); + emit populatingFailed(); +} + +void PopulateManager::deleteEngines(const QList& engines) +{ + for (PopulateEngine* engine : engines) + delete engine; +} + +void PopulateManager::interrupt() +{ + emit orderWorkerToInterrupt(); +} + +void PopulateManager::finalizePopulating(bool result) +{ + workInProgress = false; + + emit populatingFinished(); + if (result) + { + notifyInfo(tr("Table '%1' populated successfully.").arg(table)); + emit populatingSuccessful(); + } + else + emit populatingFailed(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h new file mode 100644 index 0000000..05b1f82 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h @@ -0,0 +1,48 @@ +#ifndef POPULATEMANAGER_H +#define POPULATEMANAGER_H + +#include "pluginservicebase.h" +#include "sqlitestudio.h" +#include +#include +#include + +class PopulatePlugin; +class PopulateEngine; +class Db; + +class API_EXPORT PopulateManager : public PluginServiceBase +{ + Q_OBJECT + + public: + explicit PopulateManager(QObject *parent = 0); + + void populate(Db* db, const QString& table, const QHash& engines, qint64 rows); + + private: + void error(); + void deleteEngines(const QList& engines); + + bool workInProgress = false; + Db* db = nullptr; + QString table; + QStringList columns; + QList engineList; + + public slots: + void interrupt(); + + private slots: + void finalizePopulating(bool result); + + signals: + void populatingFinished(); + void populatingSuccessful(); + void populatingFailed(); + void orderWorkerToInterrupt(); +}; + +#define POPULATE_MANAGER SQLITESTUDIO->getPopulateManager() + +#endif // POPULATEMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp new file mode 100644 index 0000000..dae8238 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp @@ -0,0 +1,1058 @@ +#include "updatemanager.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN32 +#include "JlCompress.h" +#include +#include +#endif + +// Note on creating update packages: +// Packages for Linux and MacOSX should be an archive of _contents_ of SQLiteStudio directory, +// while for Windows it should be an archive of SQLiteStudio directory itself. + +QString UpdateManager::staticErrorMessage; +UpdateManager::RetryFunction UpdateManager::retryFunction = nullptr; + +UpdateManager::UpdateManager(QObject *parent) : + QObject(parent) +{ + networkManager = new QNetworkAccessManager(this); + connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*))); + connect(this, SIGNAL(updatingError(QString)), NOTIFY_MANAGER, SLOT(error(QString))); +} + +UpdateManager::~UpdateManager() +{ + cleanup(); +} + +void UpdateManager::checkForUpdates() +{ + getUpdatesMetadata(updatesCheckReply); +} + +void UpdateManager::update() +{ + if (updatesGetUrlsReply || updatesInProgress) + return; + + getUpdatesMetadata(updatesGetUrlsReply); +} + +QString UpdateManager::getPlatformForUpdate() const +{ +#if defined(Q_OS_LINUX) + if (QSysInfo::WordSize == 64) + return "linux64"; + else + return "linux32"; +#elif defined(Q_OS_WIN) + return "win32"; +#elif defined(Q_OS_OSX) + return "macosx"; +#else + return QString(); +#endif +} + +QString UpdateManager::getCurrentVersions() const +{ + QJsonArray versionsArray; + + QJsonObject arrayEntry; + arrayEntry["component"] = "SQLiteStudio"; + arrayEntry["version"] = SQLITESTUDIO->getVersionString(); + versionsArray.append(arrayEntry); + + for (const PluginManager::PluginDetails& details : PLUGINS->getAllPluginDetails()) + { + if (details.builtIn) + continue; + + arrayEntry["component"] = details.name; + arrayEntry["version"] = details.versionString; + versionsArray.append(arrayEntry); + } + + QJsonObject topObj; + topObj["versions"] = versionsArray; + + QJsonDocument doc(topObj); + return QString::fromLatin1(doc.toJson(QJsonDocument::Compact)); +} + +bool UpdateManager::isPlatformEligibleForUpdate() const +{ + return !getPlatformForUpdate().isNull() && getDistributionType() != DistributionType::OS_MANAGED; +} + +#if defined(Q_OS_WIN32) +bool UpdateManager::executePreFinalStepWin(const QString &tempDir, const QString &backupDir, const QString &appDir, bool reqAdmin) +{ + bool res; + if (reqAdmin) + res = executeFinalStepAsRootWin(tempDir, backupDir, appDir); + else + res = executeFinalStep(tempDir, backupDir, appDir); + + if (res) + { + QFileInfo path(qApp->applicationFilePath()); + QProcess::startDetached(appDir + "/" + path.fileName(), {WIN_POST_FINAL_UPDATE_OPTION_NAME, tempDir}); + } + return res; +} +#endif + +void UpdateManager::handleAvailableUpdatesReply(QNetworkReply* reply) +{ + if (reply->error() != QNetworkReply::NoError) + { + updatingFailed(tr("An error occurred while checking for updates: %1.").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + QJsonParseError err; + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonDocument doc = QJsonDocument::fromJson(data, &err); + if (err.error != QJsonParseError::NoError) + { + qWarning() << "Invalid response from update service:" << err.errorString() << "\n" << "The data was:" << QString::fromLatin1(data); + notifyWarn(tr("Could not check available updates, because server responded with invalid message format. It is safe to ignore this warning.")); + return; + } + + QList updates = readMetadata(doc); + if (updates.size() > 0) + emit updatesAvailable(updates); + else + emit noUpdatesAvailable(); +} + +void UpdateManager::getUpdatesMetadata(QNetworkReply*& replyStoragePointer) +{ +#ifndef NO_AUTO_UPDATES + if (!isPlatformEligibleForUpdate() || replyStoragePointer) + return; + + QUrlQuery query; + query.addQueryItem("platform", getPlatformForUpdate()); + query.addQueryItem("data", getCurrentVersions()); + + QUrl url(QString::fromLatin1(updateServiceUrl) + "?" + query.query(QUrl::FullyEncoded)); + QNetworkRequest request(url); + replyStoragePointer = networkManager->get(request); +#endif +} + +void UpdateManager::handleUpdatesMetadata(QNetworkReply* reply) +{ + if (reply->error() != QNetworkReply::NoError) + { + updatingFailed(tr("An error occurred while reading updates metadata: %1.").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + QJsonParseError err; + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonDocument doc = QJsonDocument::fromJson(data, &err); + if (err.error != QJsonParseError::NoError) + { + qWarning() << "Invalid response from update service for getting metadata:" << err.errorString() << "\n" << "The data was:" << QString::fromLatin1(data); + notifyWarn(tr("Could not download updates, because server responded with invalid message format. " + "You can try again later or download and install updates manually. See
User Manual for details.").arg(manualUpdatesHelpUrl)); + return; + } + + tempDir = new QTemporaryDir(); + if (!tempDir->isValid()) { + notifyWarn(tr("Could not create temporary directory for downloading the update. Updating aborted.")); + return; + } + + updatesInProgress = true; + updatesToDownload = readMetadata(doc); + totalDownloadsCount = updatesToDownload.size(); + totalPercent = 0; + + if (totalDownloadsCount == 0) + { + updatingFailed(tr("There was no updates to download. Updating aborted.")); + return; + } + + downloadUpdates(); +} + +QList UpdateManager::readMetadata(const QJsonDocument& doc) +{ + QList updates; + UpdateEntry entry; + QJsonObject obj = doc.object(); + QJsonArray versionsArray = obj["newVersions"].toArray(); + QJsonObject entryObj; + for (const QJsonValue& value : versionsArray) + { + entryObj = value.toObject(); + entry.compontent = entryObj["component"].toString(); + entry.version = entryObj["version"].toString(); + entry.url = entryObj["url"].toString(); + updates << entry; + } + + return updates; +} + +void UpdateManager::downloadUpdates() +{ + if (updatesToDownload.size() == 0) + { + QtConcurrent::run(this, &UpdateManager::installUpdates); + return; + } + + UpdateEntry entry = updatesToDownload.takeFirst(); + currentJobTitle = tr("Downloading: %1").arg(entry.compontent); + emit updatingProgress(currentJobTitle, 0, totalPercent); + + QStringList parts = entry.url.split("/"); + if (parts.size() < 1) + { + updatingFailed(tr("Could not determinate file name from update URL: %1. Updating aborted.").arg(entry.url)); + return; + } + + QString path = tempDir->path() + QLatin1Char('/') + parts.last(); + currentDownloadFile = new QFile(path); + if (!currentDownloadFile->open(QIODevice::WriteOnly)) + { + updatingFailed(tr("Failed to open file '%1' for writting: %2. Updating aborted.").arg(path, currentDownloadFile->errorString())); + return; + } + + updatesToInstall[entry.compontent] = path; + + QNetworkRequest request(QUrl(entry.url)); + updatesGetReply = networkManager->get(request); + connect(updatesGetReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64))); + connect(updatesGetReply, SIGNAL(readyRead()), this, SLOT(readDownload())); +} + +void UpdateManager::updatingFailed(const QString& errMsg) +{ + cleanup(); + updatesInProgress = false; + emit updatingError(errMsg); +} + +void UpdateManager::installUpdates() +{ + currentJobTitle = tr("Installing updates."); + totalPercent = (totalDownloadsCount - updatesToDownload.size()) * 100 / (totalDownloadsCount + 1); + emit updatingProgress(currentJobTitle, 0, totalPercent); + + requireAdmin = doRequireAdminPrivileges(); + + QTemporaryDir installTempDir; + QString appDirName = QDir(getAppDirPath()).dirName(); + QString targetDir = installTempDir.path() + QLatin1Char('/') + appDirName; + if (!copyRecursively(getAppDirPath(), targetDir)) + { + updatingFailed(tr("Could not copy current application directory into %1 directory.").arg(installTempDir.path())); + return; + } + emit updatingProgress(currentJobTitle, 40, totalPercent); + + int i = 0; + int updatesCnt = updatesToInstall.size(); + for (const QString& component : updatesToInstall.keys()) + { + if (!installComponent(component, targetDir)) + { + cleanup(); + updatesInProgress = false; + return; + } + i++; + emit updatingProgress(currentJobTitle, (30 + (50 / updatesCnt * i)), totalPercent); + } + + if (!executeFinalStep(targetDir)) + { + cleanup(); + updatesInProgress = false; + return; + } + + currentJobTitle = QString(); + totalPercent = 100; + emit updatingProgress(currentJobTitle, 100, totalPercent); + cleanup(); + updatesInProgress = false; +#ifdef Q_OS_WIN32 + installTempDir.setAutoRemove(false); +#endif + + SQLITESTUDIO->setImmediateQuit(true); + qApp->exit(0); +} + +bool UpdateManager::executeFinalStep(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ + bool isWin = false; +#ifdef Q_OS_WIN32 + isWin = true; + + // Windows needs to wait for previus process to exit + QThread::sleep(3); + + QDir dir(backupDir); + QString dirName = dir.dirName(); + dir.cdUp(); + if (!dir.mkdir(dirName)) + { + staticUpdatingFailed(tr("Could not create directory %1.").arg(backupDir)); + return false; + } +#endif + while (!moveDir(appDir, backupDir, isWin)) + { + if (!retryFunction) + { + staticUpdatingFailed(tr("Could not rename directory %1 to %2.\nDetails: %3").arg(appDir, backupDir, staticErrorMessage)); + return false; + } + + if (!retryFunction(tr("Cannot not rename directory %1 to %2.\nDetails: %3").arg(appDir, backupDir, staticErrorMessage))) + return false; + } + + if (!moveDir(tempDir, appDir, isWin)) + { + if (!moveDir(backupDir, appDir, isWin)) + { + staticUpdatingFailed(tr("Could not move directory %1 to %2 and also failed to restore original directory, " + "so the original SQLiteStudio directory is now located at: %3").arg(tempDir, appDir, backupDir)); + } + else + { + staticUpdatingFailed(tr("Could not rename directory %1 to %2. Rolled back to the original SQLiteStudio version.").arg(tempDir, appDir)); + } + deleteDir(backupDir); + return false; + } + + deleteDir(backupDir); + return true; +} + +bool UpdateManager::handleUpdateOptions(const QStringList& argList, int& returnCode) +{ + if (argList.size() == 5 && argList[1] == UPDATE_OPTION_NAME) + { + bool result = UpdateManager::executeFinalStep(argList[2], argList[3], argList[4]); + if (result) + returnCode = 0; + else + returnCode = 1; + + return true; + } + +#ifdef Q_OS_WIN32 + if (argList.size() == 6 && argList[1] == WIN_PRE_FINAL_UPDATE_OPTION_NAME) + { + bool result = UpdateManager::executePreFinalStepWin(argList[2], argList[3], argList[4], (bool)argList[5].toInt()); + if (result) + returnCode = 0; + else + returnCode = -1; + + return true; + } + + if (argList.size() == 3 && argList[1] == WIN_POST_FINAL_UPDATE_OPTION_NAME) + { + QThread::sleep(1); // to make sure that the previous process has quit + returnCode = 0; + UpdateManager::executePostFinalStepWin(argList[2]); + return true; + } +#endif + + return false; +} + +QString UpdateManager::getStaticErrorMessage() +{ + return staticErrorMessage; +} + +bool UpdateManager::executeFinalStep(const QString& tempDir) +{ + QString appDir = getAppDirPath(); + + // Find inexisting dir name next to app dir + QDir backupDir(getBackupDir(appDir)); + +#if defined(Q_OS_WIN32) + return runAnotherInstanceForUpdate(tempDir, backupDir.absolutePath(), qApp->applicationDirPath(), requireAdmin); +#else + bool res; + if (requireAdmin) + res = executeFinalStepAsRoot(tempDir, backupDir.absolutePath(), appDir); + else + res = executeFinalStep(tempDir, backupDir.absolutePath(), appDir); + + if (res) + QProcess::startDetached(qApp->applicationFilePath(), QStringList()); + + return res; +#endif +} + +bool UpdateManager::installComponent(const QString& component, const QString& tempDir) +{ + if (!unpackToDir(updatesToInstall[component], tempDir)) + { + updatingFailed(tr("Could not unpack component %1 into %2 directory.").arg(component, tempDir)); + return false; + } + + // In future here we might also delete/change some files, according to some update script. + return true; +} + +void UpdateManager::cleanup() +{ + safe_delete(currentDownloadFile); + safe_delete(tempDir); + updatesToDownload.clear(); + updatesToInstall.clear(); + requireAdmin = false; +} + +bool UpdateManager::waitForProcess(QProcess& proc) +{ + if (!proc.waitForFinished(-1)) + { + qDebug() << "Update QProcess timed out."; + return false; + } + + if (proc.exitStatus() == QProcess::CrashExit) + { + qDebug() << "Update QProcess finished by crashing."; + return false; + } + + if (proc.exitCode() != 0) + { + qDebug() << "Update QProcess finished with code:" << proc.exitCode(); + return false; + } + + return true; +} + +QString UpdateManager::readError(QProcess& proc, bool reverseOrder) +{ + QString err = QString::fromLocal8Bit(reverseOrder ? proc.readAllStandardOutput() : proc.readAllStandardError()); + if (err.isEmpty()) + err = QString::fromLocal8Bit(reverseOrder ? proc.readAllStandardError() : proc.readAllStandardOutput()); + + QString errStr = proc.errorString(); + if (!errStr.isEmpty()) + err += "\n" + errStr; + + return err; +} + +void UpdateManager::staticUpdatingFailed(const QString& errMsg) +{ +#if defined(Q_OS_WIN32) + staticErrorMessage = errMsg; +#else + UPDATES->handleStaticFail(errMsg); +#endif + qCritical() << errMsg; +} + +bool UpdateManager::executeFinalStepAsRoot(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ +#if defined(Q_OS_LINUX) + return executeFinalStepAsRootLinux(tempDir, backupDir, appDir); +#elif defined(Q_OS_WIN32) + return executeFinalStepAsRootWin(tempDir, backupDir, appDir); +#elif defined(Q_OS_MACX) + return executeFinalStepAsRootMac(tempDir, backupDir, appDir); +#else + qCritical() << "Unknown update platform in UpdateManager::executeFinalStepAsRoot() for package" << packagePath; + return false; +#endif +} + +#if defined(Q_OS_LINUX) +bool UpdateManager::executeFinalStepAsRootLinux(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ + QStringList args = {qApp->applicationFilePath(), UPDATE_OPTION_NAME, tempDir, backupDir, appDir}; + + QProcess proc; + LinuxPermElevator elevator = findPermElevatorForLinux(); + switch (elevator) + { + case LinuxPermElevator::KDESU: + proc.setProgram("kdesu"); + args.prepend("-t"); + proc.setArguments(args); + break; + case LinuxPermElevator::GKSU: + proc.setProgram("gksu"); // TODO test gksu updates + proc.setArguments(args); + break; + case LinuxPermElevator::PKEXEC: + { + // We call CLI for doing final step, because pkexec runs cmd completly in root env, so there's no X server. + args[0] += "cli"; + + QStringList newArgs; + for (const QString& arg : args) + newArgs << wrapCmdLineArgument(arg); + + QString cmd = "cd " + wrapCmdLineArgument(qApp->applicationDirPath()) +"; " + newArgs.join(" "); + + proc.setProgram("pkexec"); + proc.setArguments({"sh", "-c", cmd}); + } + break; + case LinuxPermElevator::NONE: + updatingFailed(tr("Could not find permissions elevator application to run update as a root. Looked for: %1").arg("kdesu, gksu, pkexec")); + return false; + } + + proc.start(); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Could not execute final updating steps as root: %1").arg(readError(proc, (elevator == LinuxPermElevator::KDESU)))); + return false; + } + + return true; +} +#endif + +#ifdef Q_OS_MACX +bool UpdateManager::executeFinalStepAsRootMac(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ + // Prepare script for updater + // osascript -e "do shell script \"stufftorunasroot\" with administrator privileges" + QStringList args = {wrapCmdLineArgument(qApp->applicationFilePath() + "cli"), + UPDATE_OPTION_NAME, + wrapCmdLineArgument(tempDir), + wrapCmdLineArgument(backupDir), + wrapCmdLineArgument(appDir)}; + QProcess proc; + + QString innerCmd = wrapCmdLineArgument(args.join(" ")); + + static_qstring(scriptTpl, "do shell script %1 with administrator privileges"); + QString scriptCmd = scriptTpl.arg(innerCmd); + + // Prepare updater temporary directory + QTemporaryDir updaterDir; + if (!updaterDir.isValid()) + { + updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(tr("Cannot create temporary directory for updater."))); + return false; + } + + // Create updater script + QString scriptPath = updaterDir.path() + "/UpdateSQLiteStudio.scpt"; + QFile updaterScript(scriptPath); + if (!updaterScript.open(QIODevice::WriteOnly)) + { + updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(tr("Cannot create updater script file."))); + return false; + } + updaterScript.write(scriptCmd.toLocal8Bit()); + updaterScript.close(); + + // Compile script to updater application + QString updaterApp = updaterDir.path() + "/UpdateSQLiteStudio.app"; + proc.setProgram("osacompile"); + proc.setArguments({"-o", updaterApp, scriptPath}); + proc.start(); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(readError(proc))); + return false; + } + + // Execute updater + proc.setProgram(updaterApp + "/Contents/MacOS/applet"); + proc.setArguments({}); + proc.start(); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(readError(proc))); + return false; + } + + // Validating update + // The updater script will not return error if the user canceled the password prompt. + // We need to check if the update was actually made and return true only then. + if (QDir(tempDir).exists()) + { + // Temp dir still exists, so it was not moved by root process + updatingFailed(tr("Updating canceled.")); + return false; + } + + return true; +} +#endif + +#ifdef Q_OS_WIN32 +bool UpdateManager::executeFinalStepAsRootWin(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ + QString updateBin = qApp->applicationDirPath() + "/" + WIN_UPDATER_BINARY; + + QString installFilePath = tempDir + "/" + WIN_INSTALL_FILE; + QFile installFile(installFilePath); + installFile.open(QIODevice::WriteOnly); + QString nl("\n"); + installFile.write(UPDATE_OPTION_NAME); + installFile.write(nl.toLocal8Bit()); + installFile.write(backupDir.toLocal8Bit()); + installFile.write(nl.toLocal8Bit()); + installFile.write(appDir.toLocal8Bit()); + installFile.write(nl.toLocal8Bit()); + installFile.close(); + + int res = (int)::ShellExecuteA(0, "runas", updateBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); + if (res < 32) + { + staticUpdatingFailed(tr("Could not execute final updating steps as administrator.")); + return false; + } + + // Since I suck as a developer and I cannot implement a simple synchronous app call under Windows + // (QProcess does it somehow, but I'm too lazy to look it up and probably the solution wouldn't be compatible + // with our "privileges elevation" trick above... so after all I think we're stuck with this solution for now), + // I do the workaround here, which makes this process wait for the other process to create the "done" + // file when it's done, so this process knows when the other has ended. This way we can proceed with this + // process and we will delete some directories later on, which were required by that other process. + if (!waitForFileToDisappear(installFilePath, 10)) + { + staticUpdatingFailed(tr("Could not execute final updating steps as administrator. Updater startup timed out.")); + return false; + } + + if (!waitForFileToAppear(appDir + QLatin1Char('/') + WIN_UPDATE_DONE_FILE, 30)) + { + staticUpdatingFailed(tr("Could not execute final updating steps as administrator. Updater operation timed out.")); + return false; + } + + return true; +} +#endif + +#if defined(Q_OS_WIN32) +bool UpdateManager::executePostFinalStepWin(const QString &tempDir) +{ + QString doneFile = qApp->applicationDirPath() + QLatin1Char('/') + WIN_UPDATE_DONE_FILE; + QFile::remove(doneFile); + + QDir dir(tempDir); + dir.cdUp(); + if (!deleteDir(dir.absolutePath())) + staticUpdatingFailed(tr("Could not clean up temporary directory %1. You can delete it manually at any time.").arg(dir.absolutePath())); + + QProcess::startDetached(qApp->applicationFilePath(), QStringList()); + return true; +} + +bool UpdateManager::waitForFileToDisappear(const QString &filePath, int seconds) +{ + QFile file(filePath); + while (file.exists() && seconds > 0) + { + QThread::sleep(1); + seconds--; + } + + return !file.exists(); +} + +bool UpdateManager::waitForFileToAppear(const QString &filePath, int seconds) +{ + QFile file(filePath); + while (!file.exists() && seconds > 0) + { + QThread::sleep(1); + seconds--; + } + + return file.exists(); +} + +bool UpdateManager::runAnotherInstanceForUpdate(const QString &tempDir, const QString &backupDir, const QString &appDir, bool reqAdmin) +{ + bool res = QProcess::startDetached(tempDir + "/SQLiteStudio.exe", {WIN_PRE_FINAL_UPDATE_OPTION_NAME, tempDir, backupDir, appDir, + QString::number((int)reqAdmin)}); + if (!res) + { + updatingFailed(tr("Could not run new version for continuing update.")); + return false; + } + + return true; +} +#endif + +UpdateManager::LinuxPermElevator UpdateManager::findPermElevatorForLinux() +{ +#if defined(Q_OS_LINUX) + QProcess proc; + proc.setProgram("which"); + + if (!SQLITESTUDIO->getEnv("DISPLAY").isEmpty()) + { + proc.setArguments({"kdesu"}); + proc.start(); + if (waitForProcess(proc)) + return LinuxPermElevator::KDESU; + + proc.setArguments({"gksu"}); + proc.start(); + if (waitForProcess(proc)) + return LinuxPermElevator::GKSU; + } + + proc.setArguments({"pkexec"}); + proc.start(); + if (waitForProcess(proc)) + return LinuxPermElevator::PKEXEC; +#endif + + return LinuxPermElevator::NONE; +} + +QString UpdateManager::wrapCmdLineArgument(const QString& arg) +{ + return "\"" + escapeCmdLineArgument(arg) + "\""; +} + +QString UpdateManager::escapeCmdLineArgument(const QString& arg) +{ + if (!arg.contains("\\") && !arg.contains("\"")) + return arg; + + QString str = arg; + return str.replace("\\", "\\\\").replace("\"", "\\\""); +} + +QString UpdateManager::getBackupDir(const QString &appDir) +{ + static_qstring(bakDirTpl, "%1.old%2"); + QDir backupDir(bakDirTpl.arg(appDir, "")); + int cnt = 1; + while (backupDir.exists()) + backupDir = QDir(bakDirTpl.arg(appDir, QString::number(cnt))); + + return backupDir.absolutePath(); +} + +bool UpdateManager::unpackToDir(const QString& packagePath, const QString& outputDir) +{ +#if defined(Q_OS_LINUX) + return unpackToDirLinux(packagePath, outputDir); +#elif defined(Q_OS_WIN32) + return unpackToDirWin(packagePath, outputDir); +#elif defined(Q_OS_MACX) + return unpackToDirMac(packagePath, outputDir); +#else + qCritical() << "Unknown update platform in UpdateManager::unpackToDir() for package" << packagePath; + return false; +#endif +} + +#if defined(Q_OS_LINUX) +bool UpdateManager::unpackToDirLinux(const QString &packagePath, const QString &outputDir) +{ + QProcess proc; + proc.setWorkingDirectory(outputDir); + proc.setStandardOutputFile(QProcess::nullDevice()); + proc.setStandardErrorFile(QProcess::nullDevice()); + + if (!packagePath.endsWith("tar.gz")) + { + updatingFailed(tr("Package not in tar.gz format, cannot install: %1").arg(packagePath)); + return false; + } + + proc.start("mv", {packagePath, outputDir}); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Package %1 cannot be installed, because cannot move it to directory: %2").arg(packagePath, outputDir)); + return false; + } + + QString fileName = packagePath.split("/").last(); + QString newPath = outputDir + "/" + fileName; + proc.start("tar", {"-xzf", newPath}); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Package %1 cannot be installed, because cannot unpack it: %2").arg(packagePath, readError(proc))); + return false; + } + + QProcess::execute("rm", {"-f", newPath}); + return true; +} +#endif + +#if defined(Q_OS_MACX) +bool UpdateManager::unpackToDirMac(const QString &packagePath, const QString &outputDir) +{ + QProcess proc; + proc.setWorkingDirectory(outputDir); + proc.setStandardOutputFile(QProcess::nullDevice()); + proc.setStandardErrorFile(QProcess::nullDevice()); + + if (!packagePath.endsWith("zip")) + { + updatingFailed(tr("Package not in zip format, cannot install: %1").arg(packagePath)); + return false; + } + + proc.start("unzip", {"-o", "-d", outputDir, packagePath}); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Package %1 cannot be installed, because cannot unzip it to directory %2: %3") + .arg(packagePath, outputDir, readError(proc))); + return false; + } + + return true; +} +#endif + +#if defined(Q_OS_WIN32) +bool UpdateManager::unpackToDirWin(const QString& packagePath, const QString& outputDir) +{ + if (JlCompress::extractDir(packagePath, outputDir + "/..").isEmpty()) + { + updatingFailed(tr("Package %1 cannot be installed, because cannot unzip it to directory: %2").arg(packagePath, outputDir)); + return false; + } + + return true; +} +#endif + +void UpdateManager::handleStaticFail(const QString& errMsg) +{ + emit updatingFailed(errMsg); +} + +QString UpdateManager::getAppDirPath() const +{ + static QString appDir; + if (appDir.isNull()) + { + appDir = qApp->applicationDirPath(); +#ifdef Q_OS_MACX + QDir tmpAppDir(appDir); + tmpAppDir.cdUp(); + tmpAppDir.cdUp(); + appDir = tmpAppDir.absolutePath(); +#endif + } + return appDir; +} + +bool UpdateManager::moveDir(const QString& src, const QString& dst, bool contentsOnly) +{ + // If we're doing a rename in the very same parent directory then we don't want + // the 'move between partitions' to be involved, cause any failure to rename + // is due to permissions or file lock. + QFileInfo srcFi(src); + QFileInfo dstFi(dst); + bool sameParentDir = (srcFi.dir() == dstFi.dir()); + + QDir dir; + if (contentsOnly) + { + QString localSrc; + QString localDst; + QDir srcDir(src); + for (const QFileInfo& entry : srcDir.entryInfoList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot|QDir::Hidden|QDir::System)) + { + localSrc = entry.absoluteFilePath(); + localDst = dst + "/" + entry.fileName(); + if (!dir.rename(localSrc, localDst) && (sameParentDir || !renameBetweenPartitions(localSrc, localDst))) + { + staticUpdatingFailed(tr("Could not rename directory %1 to %2.").arg(localSrc, localDst)); + return false; + } + } + } + else + { + if (!dir.rename(src, dst) && (sameParentDir || !renameBetweenPartitions(src, dst))) + { + staticUpdatingFailed(tr("Could not rename directory %1 to %2.").arg(src, dst)); + return false; + } + } + + return true; +} + +bool UpdateManager::deleteDir(const QString& path) +{ + QDir dir(path); + if (!dir.removeRecursively()) + { + staticUpdatingFailed(tr("Could not delete directory %1.").arg(path)); + return false; + } + + return true; +} + +bool UpdateManager::execCmd(const QString& cmd, const QStringList& args, QString* errorMsg) +{ + QProcess proc; + proc.start(cmd, args); + QString cmdString = QString("%1 \"%2\"").arg(cmd, args.join("\\\" \\\"")); + + if (!waitForProcess(proc)) + { + if (errorMsg) + *errorMsg = tr("Error executing update command: %1\nError message: %2").arg(cmdString).arg(readError(proc)); + + return false; + } + + return true; +} + +void UpdateManager::setRetryFunction(const RetryFunction &value) +{ + retryFunction = value; +} + +bool UpdateManager::doRequireAdminPrivileges() +{ + QString appDirPath = getAppDirPath(); + QDir appDir(appDirPath); + bool isWritable = isWritableRecursively(appDir.absolutePath()); + + appDir.cdUp(); + QFileInfo fi(appDir.absolutePath()); + isWritable &= fi.isWritable(); + + if (isWritable) + { + QDir backupDir(getBackupDir(appDirPath)); + QString backupDirName = backupDir.dirName(); + backupDir.cdUp(); + if (backupDir.mkdir(backupDirName)) + backupDir.rmdir(backupDirName); + else + isWritable = false; + } + + return !isWritable; +} + +void UpdateManager::finished(QNetworkReply* reply) +{ + if (reply == updatesCheckReply) + { + updatesCheckReply = nullptr; + handleAvailableUpdatesReply(reply); + return; + } + + if (reply == updatesGetUrlsReply) + { + updatesGetUrlsReply = nullptr; + handleUpdatesMetadata(reply); + return; + } + + if (reply == updatesGetReply) + { + handleDownloadReply(reply); + if (reply == updatesGetReply) // if no new download is requested + updatesGetReply = nullptr; + + return; + } +} + +void UpdateManager::handleDownloadReply(QNetworkReply* reply) +{ + if (reply->error() != QNetworkReply::NoError) + { + updatingFailed(tr("An error occurred while downloading updates: %1. Updating aborted.").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + totalPercent = (totalDownloadsCount - updatesToDownload.size()) * 100 / (totalDownloadsCount + 1); + + readDownload(); + currentDownloadFile->close(); + + safe_delete(currentDownloadFile); + + reply->deleteLater(); + downloadUpdates(); +} + +void UpdateManager::downloadProgress(qint64 bytesReceived, qint64 totalBytes) +{ + int perc; + if (totalBytes < 0) + perc = -1; + else if (totalBytes == 0) + perc = 100; + else + perc = bytesReceived * 100 / totalBytes; + + emit updatingProgress(currentJobTitle, perc, totalPercent); +} + +void UpdateManager::readDownload() +{ + currentDownloadFile->write(updatesGetReply->readAll()); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h new file mode 100644 index 0000000..b8e6006 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h @@ -0,0 +1,137 @@ +#ifndef UPDATEMANAGER_H +#define UPDATEMANAGER_H + +#include "common/global.h" +#include "sqlitestudio.h" +#include +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; +class QTemporaryDir; +class QFile; + +class API_EXPORT UpdateManager : public QObject +{ + Q_OBJECT + public: + typedef std::function RetryFunction; + + struct UpdateEntry + { + QString compontent; + QString version; + QString url; + }; + + explicit UpdateManager(QObject *parent = 0); + ~UpdateManager(); + + void checkForUpdates(); + void update(); + bool isPlatformEligibleForUpdate() const; + static bool executeFinalStep(const QString& tempDir, const QString& backupDir, const QString& appDir); + static bool handleUpdateOptions(const QStringList& argList, int& returnCode); + static QString getStaticErrorMessage(); + + static void setRetryFunction(const RetryFunction &value); + + static_char* UPDATE_OPTION_NAME = "--update-final-step"; + static_char* WIN_INSTALL_FILE = "install.dat"; + static_char* WIN_UPDATE_DONE_FILE = "UpdateFinished.lck"; + + private: + enum class LinuxPermElevator + { + KDESU, + GKSU, + PKEXEC, + NONE + }; + + QString getPlatformForUpdate() const; + QString getCurrentVersions() const; + void handleAvailableUpdatesReply(QNetworkReply* reply); + void handleDownloadReply(QNetworkReply* reply); + void getUpdatesMetadata(QNetworkReply*& replyStoragePointer); + void handleUpdatesMetadata(QNetworkReply* reply); + QList readMetadata(const QJsonDocument& doc); + void downloadUpdates(); + void updatingFailed(const QString& errMsg); + void installUpdates(); + bool installComponent(const QString& component, const QString& tempDir); + bool executeFinalStep(const QString& tempDir); + bool executeFinalStepAsRoot(const QString& tempDir, const QString& backupDir, const QString& appDir); +#if defined(Q_OS_LINUX) + bool executeFinalStepAsRootLinux(const QString& tempDir, const QString& backupDir, const QString& appDir); + bool unpackToDirLinux(const QString& packagePath, const QString& outputDir); +#elif defined(Q_OS_MACX) + bool unpackToDirMac(const QString& packagePath, const QString& outputDir); + bool executeFinalStepAsRootMac(const QString& tempDir, const QString& backupDir, const QString& appDir); +#elif defined(Q_OS_WIN32) + bool runAnotherInstanceForUpdate(const QString& tempDir, const QString& backupDir, const QString& appDir, bool reqAdmin); + bool unpackToDirWin(const QString& packagePath, const QString& outputDir); +#endif + bool doRequireAdminPrivileges(); + bool unpackToDir(const QString& packagePath, const QString& outputDir); + void handleStaticFail(const QString& errMsg); + QString getAppDirPath() const; + void cleanup(); + + static bool moveDir(const QString& src, const QString& dst, bool contentsOnly = false); + static bool deleteDir(const QString& path); + static bool execCmd(const QString& cmd, const QStringList& args, QString* errorMsg = nullptr); + static bool waitForProcess(QProcess& proc); + static QString readError(QProcess& proc, bool reverseOrder = false); + static void staticUpdatingFailed(const QString& errMsg); + static LinuxPermElevator findPermElevatorForLinux(); + static QString wrapCmdLineArgument(const QString& arg); + static QString escapeCmdLineArgument(const QString& arg); + static QString getBackupDir(const QString& appDir); +#if defined(Q_OS_WIN32) + static bool executePreFinalStepWin(const QString& tempDir, const QString& backupDir, const QString& appDir, bool reqAdmin); + static bool executeFinalStepAsRootWin(const QString& tempDir, const QString& backupDir, const QString& appDir); + static bool executePostFinalStepWin(const QString& tempDir); + static bool waitForFileToDisappear(const QString& filePath, int seconds); + static bool waitForFileToAppear(const QString& filePath, int seconds); +#endif + + QNetworkAccessManager* networkManager = nullptr; + QNetworkReply* updatesCheckReply = nullptr; + QNetworkReply* updatesGetUrlsReply = nullptr; + QNetworkReply* updatesGetReply = nullptr; + bool updatesInProgress = false; + QList updatesToDownload; + QHash updatesToInstall; + QTemporaryDir* tempDir = nullptr; + QFile* currentDownloadFile = nullptr; + int totalPercent = 0; + int totalDownloadsCount = 0; + QString currentJobTitle; + bool requireAdmin = false; + static RetryFunction retryFunction; + + static QString staticErrorMessage; + static_char* WIN_PRE_FINAL_UPDATE_OPTION_NAME = "--update-pre-final-step"; + static_char* WIN_POST_FINAL_UPDATE_OPTION_NAME = "--update-post-final-step"; + static_char* WIN_UPDATER_BINARY = "UpdateSQLiteStudio.exe"; + static_char* updateServiceUrl = "http://sqlitestudio.pl/updates3.rvt"; + static_char* manualUpdatesHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Manual"; + + private slots: + void finished(QNetworkReply* reply); + void downloadProgress(qint64 bytesReceived, qint64 totalBytes); + void readDownload(); + + signals: + void updatesAvailable(const QList& updates); + void noUpdatesAvailable(); + void updatingProgress(const QString& jobTitle, int jobPercent, int totalPercent); + void updatingFinished(); + void updatingError(const QString& errorMessage); +}; + +#define UPDATES SQLITESTUDIO->getUpdateManager() + +#endif // UPDATEMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp b/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp new file mode 100644 index 0000000..201dc8b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp @@ -0,0 +1,42 @@ +#include "sqlhistorymodel.h" +#include "common/global.h" +#include "db/db.h" + +SqlHistoryModel::SqlHistoryModel(Db* db, QObject *parent) : + QueryModel(db, parent) +{ + static_char* query = "SELECT dbname, datetime(date, 'unixepoch'), (time_spent / 1000.0)||'s', rows, sql " + "FROM sqleditor_history ORDER BY date DESC"; + + setQuery(query); +} + +QVariant SqlHistoryModel::data(const QModelIndex& index, int role) const +{ + if (role == Qt::TextAlignmentRole && (index.column() == 2 || index.column() == 3)) + return (int)(Qt::AlignRight|Qt::AlignVCenter); + + return QueryModel::data(index, role); +} + +QVariant SqlHistoryModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QueryModel::headerData(section, orientation, role); + + switch (section) + { + case 0: + return tr("Database", "sql history header"); + case 1: + return tr("Execution date", "sql history header"); + case 2: + return tr("Time spent", "sql history header"); + case 3: + return tr("Rows affected", "sql history header"); + case 4: + return tr("SQL", "sql history header"); + } + + return QueryModel::headerData(section, orientation, role); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h b/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h new file mode 100644 index 0000000..0f6b25d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h @@ -0,0 +1,17 @@ +#ifndef SQLHISTORYMODEL_H +#define SQLHISTORYMODEL_H + +#include "querymodel.h" + +class Db; + +class SqlHistoryModel : public QueryModel +{ + public: + SqlHistoryModel(Db* db, QObject *parent = nullptr); + + QVariant data(const QModelIndex& index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; +}; + +#endif // SQLHISTORYMODEL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp new file mode 100644 index 0000000..10d1bd5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp @@ -0,0 +1,390 @@ +#include "sqlitestudio.h" +#include "plugins/plugin.h" +#include "services/pluginmanager.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "completionhelper.h" +#include "parser/keywords.h" +#include "parser/lexer.h" +#include "services/notifymanager.h" +#include "plugins/codeformatterplugin.h" +#include "services/codeformatter.h" +#include "plugins/generalpurposeplugin.h" +#include "plugins/dbplugin.h" +#include "common/unused.h" +#include "services/functionmanager.h" +#include "plugins/scriptingplugin.h" +#include "plugins/exportplugin.h" +#include "plugins/scriptingqt.h" +#include "plugins/dbpluginsqlite3.h" +#include "services/impl/configimpl.h" +#include "services/impl/dbmanagerimpl.h" +#include "services/impl/functionmanagerimpl.h" +#include "services/impl/collationmanagerimpl.h" +#include "services/impl/pluginmanagerimpl.h" +#include "services/updatemanager.h" +#include "impl/dbattacherimpl.h" +#include "services/exportmanager.h" +#include "services/importmanager.h" +#include "services/populatemanager.h" +#include "plugins/scriptingsql.h" +#include "plugins/importplugin.h" +#include "plugins/populateplugin.h" +#include "services/bugreporter.h" +#include "services/extralicensemanager.h" +#include +#include +#include + +DEFINE_SINGLETON(SQLiteStudio) + +static const int sqlitestudioVersion = 29906; + +SQLiteStudio::SQLiteStudio() +{ + if (qApp) // qApp is null in unit tests + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanUp())); +} + +SQLiteStudio::~SQLiteStudio() +{ +} + +ExtraLicenseManager* SQLiteStudio::getExtraLicenseManager() const +{ + return extraLicenseManager; +} + +void SQLiteStudio::setExtraLicenseManager(ExtraLicenseManager* value) +{ + extraLicenseManager = value; +} + + +bool SQLiteStudio::getImmediateQuit() const +{ + return immediateQuit; +} + +void SQLiteStudio::setImmediateQuit(bool value) +{ + immediateQuit = value; +} + +UpdateManager* SQLiteStudio::getUpdateManager() const +{ + return updateManager; +} + +void SQLiteStudio::setUpdateManager(UpdateManager* value) +{ + updateManager = value; +} + +BugReporter* SQLiteStudio::getBugReporter() const +{ + return bugReporter; +} + +void SQLiteStudio::setBugReporter(BugReporter* value) +{ + bugReporter = value; +} + +PopulateManager* SQLiteStudio::getPopulateManager() const +{ + return populateManager; +} + +void SQLiteStudio::setPopulateManager(PopulateManager* value) +{ + populateManager = value; +} + +CodeFormatter* SQLiteStudio::getCodeFormatter() const +{ + return codeFormatter; +} + +void SQLiteStudio::setCodeFormatter(CodeFormatter* codeFormatter) +{ + this->codeFormatter = codeFormatter; +} + +QString SQLiteStudio::getHomePage() const +{ + static const QString url = QStringLiteral("http://sqlitestudio.pl"); + return url; +} + +QString SQLiteStudio::getForumPage() const +{ + static const QString url = QStringLiteral("http://forum.sqlitestudio.pl"); + return url; +} + +QString SQLiteStudio::getUserManualPage() const +{ + static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual"); + return url; +} + +QString SQLiteStudio::getSqliteDocsPage() const +{ + static const QString url = QStringLiteral("http://sqlite.org/lang.html"); + return url; +} + +ImportManager* SQLiteStudio::getImportManager() const +{ + return importManager; +} + +void SQLiteStudio::setImportManager(ImportManager* value) +{ + importManager = value; +} + +ExportManager* SQLiteStudio::getExportManager() const +{ + return exportManager; +} + +void SQLiteStudio::setExportManager(ExportManager* value) +{ + exportManager = value; +} + +int SQLiteStudio::getVersion() const +{ + return sqlitestudioVersion; +} + +QString SQLiteStudio::getVersionString() const +{ + int ver = getVersion(); + int majorVer = ver / 10000; + int minorVer = ver % 10000 / 100; + int patchVer = ver % 100; + return QString::number(majorVer) + "." + QString::number(minorVer) + "." + QString::number(patchVer); +} + +CollationManager* SQLiteStudio::getCollationManager() const +{ + return collationManager; +} + +void SQLiteStudio::setCollationManager(CollationManager* value) +{ + safe_delete(collationManager); + collationManager = value; +} + +DbAttacherFactory* SQLiteStudio::getDbAttacherFactory() const +{ + return dbAttacherFactory; +} + +void SQLiteStudio::setDbAttacherFactory(DbAttacherFactory* value) +{ + safe_delete(dbAttacherFactory); + dbAttacherFactory = value; +} + +PluginManager* SQLiteStudio::getPluginManager() const +{ + return pluginManager; +} + +void SQLiteStudio::setPluginManager(PluginManager* value) +{ + safe_delete(pluginManager); + pluginManager = value; +} + +FunctionManager* SQLiteStudio::getFunctionManager() const +{ + return functionManager; +} + +void SQLiteStudio::setFunctionManager(FunctionManager* value) +{ + safe_delete(functionManager); + functionManager = value; +} + +DbManager* SQLiteStudio::getDbManager() const +{ + return dbManager; +} + +void SQLiteStudio::setDbManager(DbManager* value) +{ + safe_delete(dbManager); + dbManager = value; +} + +Config* SQLiteStudio::getConfig() const +{ + return config; +} + +void SQLiteStudio::setConfig(Config* value) +{ + safe_delete(config); + config = value; +} + +void SQLiteStudio::init(const QStringList& cmdListArguments, bool guiAvailable) +{ + env = new QProcessEnvironment(QProcessEnvironment::systemEnvironment()); + this->guiAvailable = guiAvailable; + + QThreadPool::globalInstance()->setMaxThreadCount(10); + + Q_INIT_RESOURCE(coresqlitestudio); + + CfgLazyInitializer::init(); + + initUtils(); + CfgMain::staticInit(); + Db::metaInit(); + initUtilsSql(); + initKeywords(); + Lexer::staticInit(); + CompletionHelper::init(); + + qRegisterMetaType(); + + NotifyManager::getInstance(); + + dbAttacherFactory = new DbAttacherDefaultFactory(); + + config = new ConfigImpl(); + config->init(); + + pluginManager = new PluginManagerImpl(); + dbManager = new DbManagerImpl(); + + pluginManager->registerPluginType(QObject::tr("General purpose", "plugin category name")); + pluginManager->registerPluginType(QObject::tr("Database support", "plugin category name")); + pluginManager->registerPluginType(QObject::tr("Code formatter", "plugin category name"), "formatterPluginsPage"); + pluginManager->registerPluginType(QObject::tr("Scripting languages", "plugin category name")); + pluginManager->registerPluginType(QObject::tr("Exporting", "plugin category name")); + pluginManager->registerPluginType(QObject::tr("Importing", "plugin category name")); + pluginManager->registerPluginType(QObject::tr("Table populating", "plugin category name")); + + codeFormatter = new CodeFormatter(); + connect(CFG_CORE.General.ActiveCodeFormatter, SIGNAL(changed(QVariant)), this, SLOT(updateCurrentCodeFormatter())); + connect(pluginManager, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(updateCodeFormatter())); + + // FunctionManager needs to be set up before DbManager, cause when DbManager starts up, databases make their + // connections and register functions. + functionManager = new FunctionManagerImpl(); + + collationManager = new CollationManagerImpl(); + + cmdLineArgs = cmdListArguments; + + connect(pluginManager, SIGNAL(pluginsInitiallyLoaded()), DBLIST, SLOT(notifyDatabasesAreLoaded())); + + DbPluginSqlite3* sqlite3plugin = new DbPluginSqlite3; + dynamic_cast(dbManager)->setInMemDbCreatorPlugin(sqlite3plugin); + + pluginManager->loadBuiltInPlugin(new ScriptingQt); + pluginManager->loadBuiltInPlugin(new ScriptingSql); + pluginManager->loadBuiltInPlugin(sqlite3plugin); + + exportManager = new ExportManager(); + importManager = new ImportManager(); + populateManager = new PopulateManager(); + bugReporter = new BugReporter(); + updateManager = new UpdateManager(); + extraLicenseManager = new ExtraLicenseManager(); + + extraLicenseManager->addLicense("SQLiteStudio license (GPL v3)", ":/docs/licenses/sqlitestudio_license.txt"); + extraLicenseManager->addLicense("Fugue icons", ":/docs/licenses/fugue_icons.txt"); + extraLicenseManager->addLicense("Qt, QHexEdit (LGPL v2.1)", ":/docs/licenses/lgpl.txt"); + extraLicenseManager->addLicense("diff_match (Apache License v2.0)", ":/docs/licenses/diff_match.txt"); + extraLicenseManager->addLicense("RSA library (GPL v3)", ":/docs/licenses/gpl.txt"); + +} + +void SQLiteStudio::initPlugins() +{ + pluginManager->init(); + + connect(pluginManager, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(pluginLoaded(Plugin*,PluginType*))); + connect(pluginManager, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(pluginToBeUnloaded(Plugin*,PluginType*))); + connect(pluginManager, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(pluginUnloaded(QString,PluginType*))); +} + +void SQLiteStudio::cleanUp() +{ + disconnect(pluginManager, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(pluginToBeUnloaded(Plugin*,PluginType*))); + disconnect(pluginManager, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(pluginUnloaded(QString,PluginType*))); + if (!immediateQuit) + { + pluginManager->deinit(); + safe_delete(pluginManager); // PluginManager before DbManager, so Db objects are deleted while DbManager still exists + safe_delete(updateManager); + safe_delete(bugReporter); + safe_delete(populateManager); + safe_delete(importManager); + safe_delete(exportManager); + safe_delete(functionManager); + safe_delete(extraLicenseManager); + safe_delete(dbManager); + safe_delete(config); + safe_delete(codeFormatter); + safe_delete(dbAttacherFactory); + safe_delete(env); + NotifyManager::destroy(); + } + Q_CLEANUP_RESOURCE(coresqlitestudio); +} + +void SQLiteStudio::updateCodeFormatter() +{ + codeFormatter->fullUpdate(); +} + +void SQLiteStudio::updateCurrentCodeFormatter() +{ + codeFormatter->updateCurrent(); +} + +void SQLiteStudio::pluginLoaded(Plugin* plugin, PluginType* pluginType) +{ + UNUSED(plugin); + if (pluginType->isForPluginType()) // TODO move this to slot of CodeFormatter + updateCodeFormatter(); +} + +void SQLiteStudio::pluginToBeUnloaded(Plugin* plugin, PluginType* pluginType) +{ + UNUSED(plugin); + UNUSED(pluginType); +} + +void SQLiteStudio::pluginUnloaded(const QString& pluginName, PluginType* pluginType) +{ + UNUSED(pluginName); + if (pluginType->isForPluginType()) // TODO move this to slot of CodeFormatter + updateCodeFormatter(); +} + +QString SQLiteStudio::getEnv(const QString &name, const QString &defaultValue) +{ + return env->value(name, defaultValue); +} + +DbAttacher* SQLiteStudio::createDbAttacher(Db* db) +{ + return dbAttacherFactory->create(db); +} + +bool SQLiteStudio::isGuiAvailable() const +{ + return guiAvailable; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h new file mode 100644 index 0000000..a41867a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h @@ -0,0 +1,258 @@ +#ifndef SQLITESTUDIO_H +#define SQLITESTUDIO_H + +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include "services/config.h" +#include +#include +#include + +class DbManager; +class Config; +class QProcessEnvironment; +class PluginManager; +class QThreadPool; +class NotifyManager; +class CodeFormatter; +class Plugin; +class PluginType; +class FunctionManager; +class DbAttacherFactory; +class DbAttacher; +class ExportManager; +class ImportManager; +class PopulateManager; +class PluginLoadingHandler; +class BugReporter; +class UpdateManager; +class ExtraLicenseManager; + +/** @file */ + +/** + * @mainpage + * SQLiteStudio is SQLite 2 and 3 manager for Windows, MacOS X and Linux. + * + * Global variables and macros: + *
    + *
  • #SQLITESTUDIO - access point to all services (singleton instance of SQLiteStudio)
  • + *
  • #PLUGINS - quick access to PluginManager
  • + *
  • #DBLIST - quick access to DbManager
  • + *
  • #FUNCTIONS - quick access to FunctionManager
  • + *
  • #CFG - quick access to Config
  • + *
+ */ + + +/** + * @brief Main application class. + * This is an entry point for all services. + * Get all managers, services, etc from this class. + * It is a singleton. + */ +class API_EXPORT SQLiteStudio : public QObject +{ + Q_OBJECT + + DECLARE_SINGLETON(SQLiteStudio) + + public: + /** + * @brief Initializes SQLiteStudio object. + * @param cmdListArguments Command line arguments. + * @param pluginLoadingHandler Factory for producing plugin loader. + * + * Initialization process involves creating of all internal objects (managers, etc.) + * and reading necessary configuration. It also interpreted command line arguments + * and applies them. + * + * The plugin loader factory (handler) is used to solve issue with GUI symbols visibility. while loading code being placed in the core shared library. + * It should be null when starting SQLiteStudio in CLI mode and not null when starting GUI client. See PluginLoadingHandler for more details on that. + * + * See parseCmdLineArgs() for details on supported options. + */ + void init(const QStringList& cmdListArguments, bool guiAvailable); + + void initPlugins(); + + /** + * @brief Gets environment variable value. + * @param name Name of the environment variable. + * @param defaultValue Default value to be returned if the environment variable is not defined. + * @return Either value of the environment variable - if defined - or the value passed as defaultValue. + * + * This method provides cross-platform way to get environment variable value. + * Internally it uses QProcessEnvironment, but while it's expensive to initialize it every time you access environment, + * it keeps the single instance of that object and lets you query variables by name. + */ + QString getEnv(const QString& name, const QString& defaultValue = QString()); + + /** + * @brief Creates new DbAttacher instance for given database. + * @param db Database to create attacher for. + * @return Attacher instance. + */ + DbAttacher* createDbAttacher(Db* db); + + bool isGuiAvailable() const; + + Config* getConfig() const; + void setConfig(Config* value); + + DbManager* getDbManager() const; + void setDbManager(DbManager* value); + + FunctionManager* getFunctionManager() const; + void setFunctionManager(FunctionManager* value); + + PluginManager* getPluginManager() const; + void setPluginManager(PluginManager* value); + + DbAttacherFactory* getDbAttacherFactory() const; + void setDbAttacherFactory(DbAttacherFactory* value); + + CollationManager* getCollationManager() const; + void setCollationManager(CollationManager* value); + + ExportManager* getExportManager() const; + void setExportManager(ExportManager* value); + + int getVersion() const; + QString getVersionString() const; + + ImportManager* getImportManager() const; + void setImportManager(ImportManager* value); + + PopulateManager* getPopulateManager() const; + void setPopulateManager(PopulateManager* value); + + CodeFormatter* getCodeFormatter() const; + void setCodeFormatter(CodeFormatter* codeFormatter); + + BugReporter* getBugReporter() const; + void setBugReporter(BugReporter* value); + + QString getHomePage() const; + QString getForumPage() const; + QString getUserManualPage() const; + QString getSqliteDocsPage() const; + + UpdateManager* getUpdateManager() const; + void setUpdateManager(UpdateManager* value); + + bool getImmediateQuit() const; + void setImmediateQuit(bool value); + + ExtraLicenseManager* getExtraLicenseManager() const; + void setExtraLicenseManager(ExtraLicenseManager* value); + + private: + /** + * @brief Creates singleton instance. + * + * It doesn't initialize anything, just constructs object. + * Initialization of member data is done by init() method. + */ + SQLiteStudio(); + + /** + * @brief Deinitializes object. + * + * Calls cleanUp(). + */ + ~SQLiteStudio(); + + /** + * @brief Code formatter service. + */ + CodeFormatter* codeFormatter = nullptr; + + /** + * @brief The application environment. + * + * This variable represents environment of the application. + * It provides access to environment variables. + */ + QProcessEnvironment* env = nullptr; + + /** + * @brief List of command line arguments. + * + * It's a copy of arguments passed to application in command line. + */ + QStringList cmdLineArgs; + + bool guiAvailable = false; + bool immediateQuit = false; + Config* config = nullptr; + DbManager* dbManager = nullptr; + FunctionManager* functionManager = nullptr; + PluginManager* pluginManager = nullptr; + DbAttacherFactory* dbAttacherFactory = nullptr; + CollationManager* collationManager = nullptr; + ExportManager* exportManager = nullptr; + ImportManager* importManager = nullptr; + PopulateManager* populateManager = nullptr; + BugReporter* bugReporter = nullptr; + UpdateManager* updateManager = nullptr; + ExtraLicenseManager* extraLicenseManager = nullptr; + + private slots: + void pluginLoaded(Plugin* plugin,PluginType* pluginType); + void pluginToBeUnloaded(Plugin* plugin,PluginType* pluginType); + void pluginUnloaded(const QString& pluginName,PluginType* pluginType); + + /** + * @brief Cleans up all internal objects. + * + * Deletes all internal objects. It's called from destructor. + */ + void cleanUp(); + + public slots: + /** + * @brief Updates code formatter with available plugins. + * + * Calls CodeFormatter's fullUpdate() method to read available formatters. + * This also reads formatters selected in config. + */ + void updateCodeFormatter(); + + /** + * @brief Updates code formater with selected plugins. + * + * Doesn't change list of available formatters, but reads new selected formatters from config. + */ + void updateCurrentCodeFormatter(); +}; + +/** + * @def SQLITESTUDIO + * @brief Global entry point for application services. + * + * This macro actually calls SQLiteStudio::getInstance(), which returns singleton instance + * of the main class, which is SQLiteStudio. Use this class as starting point + * to access all services of the application (database manager, plugins manager, etc). + * This singleton instance is created at the very begining of application start (in main()) + * and so can be used from pretty much everywhere in the code. + * + * Quick example of getting all databases registered in the application, iterating through them and printing + * their name to standard output: + * @code + #include "qio.h" + #include "sqlitestudio.h" + + void someFunction() + { + QList dblist = SQLITESTUDIO->getDbManager()->getDbList(); + foreach (Db* db, dblist) + { + qOut << db->getName(); + } + } + @endcode + */ +#define SQLITESTUDIO SQLiteStudio::getInstance() + +#endif // SQLITESTUDIO_H diff --git a/SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp b/SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp new file mode 100644 index 0000000..7555714 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp @@ -0,0 +1,695 @@ +#include "tablemodifier.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include "schemaresolver.h" +#include "selectresolver.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitedelete.h" +#include + +// TODO no attach/temp db name support in this entire class +// mainly in calls to schema resolver, but maybe other stuff too + +TableModifier::TableModifier(Db* db, const QString& table) : + db(db), + table(table) +{ + init(); +} + +TableModifier::TableModifier(Db* db, const QString& database, const QString& table) : + db(db), + database(database), + table(table) +{ + init(); +} + +void TableModifier::alterTable(SqliteCreateTablePtr newCreateTable) +{ + tableColMap = newCreateTable->getModifiedColumnsMap(true); + existingColumns = newCreateTable->getColumnNames(); + newName = newCreateTable->table; + + QString tempTableName; + if (table.compare(newName, Qt::CaseInsensitive) == 0) + tempTableName = renameToTemp(); + + newCreateTable->rebuildTokens(); + sqls << newCreateTable->detokenize(); + copyDataTo(newCreateTable); + + // If temp table was created, it means that table name hasn't changed. In that case we need to cleanup temp table (drop it). + // Otherwise, the table name has changed, therefor there still remains the old table which we copied data from - we need to drop it here. + sqls << QString("DROP TABLE %1;").arg(wrapObjIfNeeded(tempTableName.isNull() ? originalTable : tempTableName, dialect)); + + handleFks(); + handleIndexes(); + handleTriggers(); + handleViews(); +} + +void TableModifier::renameTo(const QString& newName) +{ + if (!createTable) + return; + + if (dialect == Dialect::Sqlite3) + { + sqls << QString("ALTER TABLE %1 RENAME TO %2;").arg(wrapObjIfNeeded(table, dialect), wrapObjIfNeeded(newName, dialect)); + } + else + { + sqls << QString("CREATE TABLE %1 AS SELECT * FROM %2;").arg(wrapObjIfNeeded(newName, dialect), wrapObjIfNeeded(table, dialect)) + << QString("DROP TABLE %1;").arg(wrapObjIfNeeded(table, dialect)); + } + + table = newName; + createTable->table = newName; +} + +QString TableModifier::renameToTemp() +{ + QString name = getTempTableName(); + renameTo(name); + return name; +} + +void TableModifier::copyDataTo(const QString& targetTable) +{ + SchemaResolver resolver(db); + QStringList targetColumns = resolver.getTableColumns(targetTable); + QStringList colsToCopy; + foreach (SqliteCreateTable::Column* column, createTable->columns) + if (targetColumns.contains(column->name, Qt::CaseInsensitive)) + colsToCopy << wrapObjIfNeeded(column->name, dialect); + + copyDataTo(targetTable, colsToCopy, colsToCopy); +} + +void TableModifier::handleFks() +{ + SchemaResolver resolver(db); + + QStringList fkTables = resolver.getFkReferencingTables(originalTable); + + foreach (const QString& fkTable, fkTables) + { + TableModifier subModifier(db, fkTable); + if (!subModifier.isValid()) + { + warnings << QObject::tr("Table %1 is referencing table %2, but the foreign key definition will not be updated for new table definition " + "due to problems while parsing DDL of the table %3.").arg(fkTable, originalTable, fkTable); + continue; + } + + subModifier.tableColMap = tableColMap; + subModifier.existingColumns = existingColumns; + subModifier.newName = newName; + subModifier.subHandleFks(originalTable); + sqls += subModifier.generateSqls(); + modifiedTables << fkTable; + + modifiedTables += subModifier.getModifiedTables(); + modifiedIndexes += subModifier.getModifiedIndexes(); + modifiedTriggers += subModifier.getModifiedTriggers(); + modifiedViews += subModifier.getModifiedViews(); + + warnings += subModifier.getWarnings(); + errors += subModifier.getErrors(); + } +} + +void TableModifier::subHandleFks(const QString& oldName) +{ + bool modified = false; + foreach (SqliteCreateTable::Constraint* fk, createTable->getForeignKeysByTable(oldName)) + { + if (subHandleFks(fk->foreignKey, oldName)) + modified = true; + } + + foreach (SqliteCreateTable::Column::Constraint* fk, createTable->getColumnForeignKeysByTable(oldName)) + { + if (subHandleFks(fk->foreignKey, oldName)) + modified = true; + } + + if (!modified) + return; + + QString tempName = renameToTemp(); + + createTable->table = originalTable; + createTable->rebuildTokens(); + sqls << createTable->detokenize(); + + copyDataTo(originalTable); + + sqls << QString("DROP TABLE %1;").arg(wrapObjIfNeeded(tempName, dialect)); + + simpleHandleIndexes(); + simpleHandleTriggers(); +} + +bool TableModifier::subHandleFks(SqliteForeignKey* fk, const QString& oldName) +{ + bool modified = false; + + // Table + if (handleName(oldName, fk->foreignTable)) + modified = true; + + // Columns + if (handleIndexedColumns(fk->indexedColumns)) + modified = true; + + return modified; +} + +bool TableModifier::handleName(const QString& oldName, QString& valueToUpdate) +{ + if (newName.compare(oldName, Qt::CaseInsensitive) == 0) + return false; + + if (valueToUpdate.compare(oldName, Qt::CaseInsensitive) == 0) + { + valueToUpdate = newName; + return true; + } + return false; +} + +bool TableModifier::handleIndexedColumns(QList& columnsToUpdate) +{ + bool modified = false; + QString lowerName; + QMutableListIterator it(columnsToUpdate); + while (it.hasNext()) + { + SqliteIndexedColumn* idxCol = it.next(); + + // If column was modified, assign new name + lowerName = idxCol->name.toLower(); + if (tableColMap.contains(lowerName)) + { + idxCol->name = tableColMap[lowerName]; + modified = true; + continue; + } + + // It wasn't modified, but it's not on existing columns list? Remove it. + if (indexOf(existingColumns, idxCol->name, Qt::CaseInsensitive) == -1) + { + it.remove(); + modified = true; + } + } + return modified; +} + +bool TableModifier::handleColumnNames(QStringList& columnsToUpdate) +{ + bool modified = false; + QString lowerName; + QMutableStringListIterator it(columnsToUpdate); + while (it.hasNext()) + { + it.next(); + + // If column was modified, assign new name + lowerName = it.value().toLower(); + if (tableColMap.contains(lowerName)) + { + it.value() = tableColMap[lowerName]; + modified = true; + continue; + } + + // It wasn't modified, but it's not on existing columns list? Remove it. + if (indexOf(existingColumns, it.value(), Qt::CaseInsensitive) == -1) + { + it.remove(); + modified = true; + } + } + return modified; +} + +bool TableModifier::handleColumnTokens(TokenList& columnsToUpdate) +{ + bool modified = false; + QString lowerName; + QMutableListIterator it(columnsToUpdate); + while (it.hasNext()) + { + TokenPtr token = it.next(); + + // If column was modified, assign new name + lowerName = token->value.toLower(); + if (tableColMap.contains(lowerName)) + { + token->value = tableColMap[lowerName]; + modified = true; + continue; + } + + // It wasn't modified, but it's not on existing columns list? + // In case of SELECT it's complicated to remove that token from anywhere + // in the statement. Replacing it with NULL is a kind of compromise. + if (indexOf(existingColumns, token->value, Qt::CaseInsensitive) == -1) + { + token->value = "NULL"; + modified = true; + } + } + return modified; +} + +bool TableModifier::handleUpdateColumns(SqliteUpdate* update) +{ + bool modified = false; + QString lowerName; + QMutableListIterator it(update->keyValueMap); + while (it.hasNext()) + { + it.next(); + + // If column was modified, assign new name + lowerName = it.value().first.toLower(); + if (tableColMap.contains(lowerName)) + { + it.value().first = tableColMap[lowerName]; + modified = true; + continue; + } + + // It wasn't modified, but it's not on existing columns list? Remove it. + if (indexOf(existingColumns, it.value().first, Qt::CaseInsensitive) == -1) + { + it.remove(); + modified = true; + } + } + return modified; +} + +QStringList TableModifier::getModifiedViews() const +{ + return modifiedViews; +} + +bool TableModifier::hasMessages() const +{ + return errors.size() > 0 || warnings.size() > 0; +} + +QStringList TableModifier::getModifiedTriggers() const +{ + return modifiedTriggers; +} + +QStringList TableModifier::getModifiedIndexes() const +{ + return modifiedIndexes; +} + +QStringList TableModifier::getModifiedTables() const +{ + return modifiedTables; +} + +void TableModifier::copyDataTo(SqliteCreateTablePtr newCreateTable) +{ + QStringList existingColumns = createTable->getColumnNames(); + + QStringList srcCols; + QStringList dstCols; + foreach (SqliteCreateTable::Column* column, newCreateTable->columns) + { + if (!existingColumns.contains(column->originalName)) + continue; // not copying columns that didn't exist before + + srcCols << wrapObjIfNeeded(column->originalName, dialect); + dstCols << wrapObjIfNeeded(column->name, dialect); + } + + copyDataTo(newCreateTable->table, srcCols, dstCols); +} + +void TableModifier::handleIndexes() +{ + SchemaResolver resolver(db); + QList parsedIndexesForTable = resolver.getParsedIndexesForTable(originalTable); + foreach (SqliteCreateIndexPtr index, parsedIndexesForTable) + handleIndex(index); +} + +void TableModifier::handleIndex(SqliteCreateIndexPtr index) +{ + handleName(originalTable, index->table); + handleIndexedColumns(index->indexedColumns); + index->rebuildTokens(); + sqls << index->detokenize(); + modifiedIndexes << index->index; + + // TODO partial index needs handling expr here +} + +void TableModifier::handleTriggers() +{ + SchemaResolver resolver(db); + QList parsedTriggersForTable = resolver.getParsedTriggersForTable(originalTable, true); + foreach (SqliteCreateTriggerPtr trig, parsedTriggersForTable) + handleTrigger(trig); +} + +void TableModifier::handleTrigger(SqliteCreateTriggerPtr trigger) +{ + handleName(originalTable, trigger->table); + if (trigger->event->type == SqliteCreateTrigger::Event::UPDATE_OF) + handleColumnNames(trigger->event->columnNames); + + SqliteQuery* newQuery = nullptr; + QList newQueries; + foreach (SqliteQuery* query, trigger->queries) + { + // The handleTriggerQuery() may delete the input query object. Don't refer to it later. + newQuery = handleTriggerQuery(query, trigger->trigger); + if (newQuery) + newQueries << newQuery; + else + errors << QObject::tr("Cannot not update trigger %1 according to table %2 modification.").arg(trigger->trigger, originalTable); + } + trigger->queries = newQueries; + + trigger->rebuildTokens(); + sqls << trigger->detokenize(); + modifiedTriggers << trigger->trigger; +} + +void TableModifier::handleViews() +{ + SchemaResolver resolver(db); + QList parsedViewsForTable = resolver.getParsedViewsForTable(originalTable); + foreach (SqliteCreateViewPtr view, parsedViewsForTable) + handleView(view); +} + +void TableModifier::handleView(SqliteCreateViewPtr view) +{ + SqliteSelect* newSelect = handleSelect(view->select); + if (!newSelect) + { + errors << QObject::tr("Cannot not update view %1 according to table %2 modifications.\nThe view will remain as it is.").arg(view->view, originalTable); + return; + } + + delete view->select; + view->select = newSelect; + view->select->setParent(view.data()); + view->rebuildTokens(); + + sqls << QString("DROP VIEW %1;").arg(wrapObjIfNeeded(view->view, dialect)); + sqls << view->detokenize(); + + simpleHandleTriggers(view->view); + + modifiedViews << view->view; +} + +SqliteQuery* TableModifier::handleTriggerQuery(SqliteQuery* query, const QString& trigName) +{ + SqliteSelect* select = dynamic_cast(query); + if (select) + return handleSelect(select); + + SqliteUpdate* update = dynamic_cast(query); + if (update) + return handleTriggerUpdate(update, trigName); + + SqliteInsert* insert = dynamic_cast(query); + if (insert) + return handleTriggerInsert(insert, trigName); + + SqliteDelete* del = dynamic_cast(query); + if (del) + return handleTriggerDelete(del, trigName); + + return nullptr; +} + +SqliteSelect* TableModifier::handleSelect(SqliteSelect* select) +{ + // Table name + TokenList tableTokens = select->getContextTableTokens(false); + foreach (TokenPtr token, tableTokens) + { + if (token->value.compare(originalTable, Qt::CaseInsensitive) == 0) + token->value = newName; + } + + // Column names + TokenList columnTokens = select->getContextColumnTokens(false); + SelectResolver selectResolver(db, select->detokenize()); + QList columns = selectResolver.translateToColumns(select, columnTokens); + + TokenList columnTokensToChange; + for (int i = 0; i < columnTokens.size(); i++) + { + if (columns[i].type != SelectResolver::Column::COLUMN) + continue; + + if (originalTable.compare(columns[i].table, Qt::CaseInsensitive) == 0) + columnTokensToChange << columnTokens[i]; + } + + handleColumnTokens(columnTokensToChange); + + // Rebuilding modified tokens into the select object + QString selectSql = select->detokenize(); + SqliteQueryPtr queryPtr = parseQuery(selectSql); + if (!queryPtr) + { + qCritical() << "Could not parse modified SELECT in TableModifier::handleSelect()."; + return nullptr; + } + + SqliteSelectPtr selectPtr = queryPtr.dynamicCast(); + if (!selectPtr) + { + qCritical() << "Could cast into SELECT in TableModifier::handleSelect()."; + return nullptr; + } + + return new SqliteSelect(*selectPtr.data()); +} + +SqliteUpdate* TableModifier::handleTriggerUpdate(SqliteUpdate* update, const QString& trigName) +{ + // Table name + if (update->table.compare(originalTable, Qt::CaseInsensitive) == 0) + update->table = newName; + + // Column names + handleUpdateColumns(update); + + // Any embedded selects + bool embedSelectsOk = handleSubSelects(update); + if (!embedSelectsOk) + { + warnings << QObject::tr("There is a problem with updating an %1 statement within %2 trigger. " + "One of the SELECT substatements which might be referring to table %3 cannot be properly modified. " + "Manual update of the trigger may be necessary.").arg("UPDATE").arg(trigName).arg(originalTable); + } + + return update; +} + +SqliteInsert* TableModifier::handleTriggerInsert(SqliteInsert* insert, const QString& trigName) +{ + // Table name + if (insert->table.compare(originalTable, Qt::CaseInsensitive) == 0) + insert->table = newName; + + // Column names + handleColumnNames(insert->columnNames); + + // Any embedded selects + bool embedSelectsOk = handleSubSelects(insert); + if (!embedSelectsOk) + { + warnings << QObject::tr("There is a problem with updating an %1 statement within %2 trigger. " + "One of the SELECT substatements which might be referring to table %3 cannot be properly modified. " + "Manual update of the trigger may be necessary.").arg("INSERT", trigName, originalTable); + } + + return insert; +} + +SqliteDelete* TableModifier::handleTriggerDelete(SqliteDelete* del, const QString& trigName) +{ + // Table name + if (del->table.compare(originalTable, Qt::CaseInsensitive) == 0) + del->table = newName; + + // Any embedded selects + bool embedSelectsOk = handleSubSelects(del); + if (!embedSelectsOk) + { + warnings << QObject::tr("There is a problem with updating an %1 statement within %2 trigger. " + "One of the SELECT substatements which might be referring to table %3 cannot be properly modified. " + "Manual update of the trigger may be necessary.").arg("DELETE", trigName, originalTable); + } + + return del; +} + +bool TableModifier::handleSubSelects(SqliteStatement* stmt) +{ + bool embedSelectsOk = true; + QList selects = stmt->getAllTypedStatements(); + SqliteExpr* expr = nullptr; + foreach (SqliteSelect* select, selects) + { + expr = dynamic_cast(select->parentStatement()); + if (!expr) + { + embedSelectsOk = false; + continue; + } + + if (!handleExprWithSelect(expr)) + embedSelectsOk = false; + } + return embedSelectsOk; +} + +bool TableModifier::handleExprWithSelect(SqliteExpr* expr) +{ + if (!expr->select) + { + qCritical() << "No SELECT in TableModifier::handleExprWithSelect()"; + return false; + } + + SqliteSelect* newSelect = handleSelect(expr->select); + if (!newSelect) + { + qCritical() << "Could not generate new SELECT in TableModifier::handleExprWithSelect()"; + return false; + } + + delete expr->select; + expr->select = newSelect; + expr->select->setParent(expr); + return true; +} + +void TableModifier::simpleHandleIndexes() +{ + SchemaResolver resolver(db); + QList parsedIndexesForTable = resolver.getParsedIndexesForTable(originalTable); + foreach (SqliteCreateIndexPtr index, parsedIndexesForTable) + sqls << index->detokenize(); +} + +void TableModifier::simpleHandleTriggers(const QString& view) +{ + SchemaResolver resolver(db); + QList parsedTriggers ; + if (!view.isNull()) + parsedTriggers = resolver.getParsedTriggersForView(view); + else + parsedTriggers = resolver.getParsedTriggersForTable(originalTable); + + foreach (SqliteCreateTriggerPtr trig, parsedTriggers) + sqls << trig->detokenize(); +} + +SqliteQueryPtr TableModifier::parseQuery(const QString& ddl) +{ + Parser parser(dialect); + if (!parser.parse(ddl) || parser.getQueries().size() == 0) + return SqliteQueryPtr(); + + return parser.getQueries().first(); +} + +void TableModifier::copyDataTo(const QString& targetTable, const QStringList& srcCols, const QStringList& dstCols) +{ + sqls << QString("INSERT INTO %1 (%2) SELECT %3 FROM %4;").arg(wrapObjIfNeeded(targetTable, dialect), dstCols.join(", "), srcCols.join(", "), + wrapObjIfNeeded(table, dialect)); +} + +QStringList TableModifier::generateSqls() const +{ + return sqls; +} + +bool TableModifier::isValid() const +{ + return !createTable.isNull(); +} + +QStringList TableModifier::getErrors() const +{ + return errors; +} + +QStringList TableModifier::getWarnings() const +{ + return warnings; +} + +void TableModifier::init() +{ + dialect = db->getDialect(); + originalTable = table; + parseDdl(); +} + +void TableModifier::parseDdl() +{ + SchemaResolver resolver(db); + QString ddl = resolver.getObjectDdl(database, table, SchemaResolver::TABLE); + if (ddl.isNull()) + { + qCritical() << "Could not find object's ddl while parsing table ddl in the TableModifier."; + return; + } + + Parser parser(dialect); + if (!parser.parse(ddl)) + { + qCritical() << "Could not parse table's' ddl in the TableModifier. The ddl is:" << ddl; + return; + } + + if (parser.getQueries().size() != 1) + { + qCritical() << "Parsed ddl produced more or less than 1 query in the TableModifier. The ddl is:" << ddl; + return; + } + + SqliteQueryPtr query = parser.getQueries().first(); + SqliteCreateTablePtr createTable = query.dynamicCast(); + if (!createTable) + { + qCritical() << "Parsed ddl produced something else than CreateTable statement in the TableModifier. The ddl is:" << ddl; + return; + } + + this->createTable = createTable; +} + +QString TableModifier::getTempTableName() const +{ + SchemaResolver resolver(db); + return resolver.getUniqueName("sqlitestudio_temp_table"); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/tablemodifier.h b/SQLiteStudio3/coreSQLiteStudio/tablemodifier.h new file mode 100644 index 0000000..6a39b33 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/tablemodifier.h @@ -0,0 +1,114 @@ +#ifndef TABLEMODIFIER_H +#define TABLEMODIFIER_H + +#include "db/db.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" + +class API_EXPORT TableModifier +{ + public: + TableModifier(Db* db, const QString& table); + TableModifier(Db* db, const QString& database, const QString& table); + + void alterTable(SqliteCreateTablePtr newCreateTable); + + QStringList generateSqls() const; + bool isValid() const; + QStringList getErrors() const; + QStringList getWarnings() const; + QStringList getModifiedTables() const; + QStringList getModifiedIndexes() const; + QStringList getModifiedTriggers() const; + QStringList getModifiedViews() const; + bool hasMessages() const; + + private: + void init(); + void parseDdl(); + QString getTempTableName() const; + void copyDataTo(const QString& targetTable, const QStringList& srcCols, const QStringList& dstCols); + void renameTo(const QString& newName); + QString renameToTemp(); + void copyDataTo(const QString& table); + void copyDataTo(SqliteCreateTablePtr newCreateTable); + + void handleIndexes(); + void handleIndex(SqliteCreateIndexPtr index); + void handleTriggers(); + void handleTrigger(SqliteCreateTriggerPtr trigger); + void handleViews(); + void handleView(SqliteCreateViewPtr view); + SqliteQuery* handleTriggerQuery(SqliteQuery* query, const QString& trigName); + SqliteSelect* handleSelect(SqliteSelect* select); + SqliteUpdate* handleTriggerUpdate(SqliteUpdate* update, const QString& trigName); + SqliteInsert* handleTriggerInsert(SqliteInsert* insert, const QString& trigName); + SqliteDelete* handleTriggerDelete(SqliteDelete* del, const QString& trigName); + bool handleSubSelects(SqliteStatement* stmt); + bool handleExprWithSelect(SqliteExpr* expr); + void simpleHandleIndexes(); + void simpleHandleTriggers(const QString& view = QString::null); + SqliteQueryPtr parseQuery(const QString& ddl); + + /** + * @brief alterTableHandleFks + * @param newCreateTable + * Finds all tables referencing currently modified table and updates their referenced table name and columns. + */ + void handleFks(); + void subHandleFks(const QString& oldName); + bool subHandleFks(SqliteForeignKey* fk, const QString& oldName); + + bool handleName(const QString& oldName, QString& valueToUpdate); + bool handleIndexedColumns(QList& columnsToUpdate); + bool handleColumnNames(QStringList& columnsToUpdate); + bool handleColumnTokens(TokenList& columnsToUpdate); + bool handleUpdateColumns(SqliteUpdate* update); + + Db* db = nullptr; + Dialect dialect; + + /** + * @brief database Database name. The "main" is default. + * Other databases (temp, attached...) are not supported at the moment. + */ + QString database; + + /** + * @brief table Current table name (after renaming) + */ + QString table; + + /** + * @brief originalTable Initial table name, before any renaming. + */ + QString originalTable; + + /** + * @brief createTable Original DDL. + */ + SqliteCreateTablePtr createTable; + + /** + * @brief sqls Statements to be executed to make changes real. + */ + QStringList sqls; + + QStringList warnings; + QStringList errors; + + QString newName; + QStringList existingColumns; + QHash tableColMap; + QStringList modifiedTables; + QStringList modifiedIndexes; + QStringList modifiedTriggers; + QStringList modifiedViews; +}; + +#endif // TABLEMODIFIER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp b/SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp new file mode 100644 index 0000000..486763b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp @@ -0,0 +1,103 @@ +#include "tsvserializer.h" + +#ifdef Q_OS_MACX +QString TsvSerializer::rowSeparator = "\r"; +#else +QString TsvSerializer::rowSeparator = "\n"; +#endif +QString TsvSerializer::columnSeparator = "\t"; + +QString TsvSerializer::serialize(const QList& data) +{ + QStringList outputRows; + + foreach (const QStringList& dataRow, data) + outputRows << serialize(dataRow); + + return outputRows.join(rowSeparator); +} + +QString TsvSerializer::serialize(const QStringList& data) +{ + QString value; + bool hasQuote; + QStringList outputCells; + foreach (const QString& rowValue, data) + { + value = rowValue; + + hasQuote = value.contains("\""); + if (value.contains(columnSeparator) || value.contains(rowSeparator)) + { + if (hasQuote) + value.replace("\"", "\"\""); + + value = "\""+value+"\""; + } + + outputCells << value; + } + + return outputCells.join(columnSeparator); +} + +QList TsvSerializer::deserialize(const QString& data) +{ + QList rows; + QStringList cells; + + int pos = 0; + int lgt = data.length(); + bool quotes = false; + QString field = ""; + QChar c; + + while (pos < lgt) + { + c = data[pos]; + if (!quotes && c == '"' ) + { + if (field.isEmpty()) + quotes = true; + else + field += c; + } + else if (quotes && c == '"' ) + { + if (pos + 1 < data.length() && data[pos+1] == '"' ) + { + field += c; + pos++; + } + else + { + quotes = false; + } + } + else if (!quotes && c == columnSeparator) + { + cells << field; + field.clear(); + } + else if (!quotes && c == rowSeparator) + { + cells << field; + rows << cells; + cells.clear(); + field.clear(); + } + else + { + field += c; + } + pos++; + } + + if (field.size() > 0) + cells << field; + + if (cells.size() > 0) + rows << cells; + + return rows; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/tsvserializer.h b/SQLiteStudio3/coreSQLiteStudio/tsvserializer.h new file mode 100644 index 0000000..efb3934 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/tsvserializer.h @@ -0,0 +1,20 @@ +#ifndef TSVSERIALIZER_H +#define TSVSERIALIZER_H + +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include + +class API_EXPORT TsvSerializer +{ + public: + static QString serialize(const QList& data); + static QString serialize(const QStringList& data); + static QList deserialize(const QString& data); + + private: + static QString rowSeparator; + static QString columnSeparator; +}; + +#endif // TSVSERIALIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp b/SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp new file mode 100644 index 0000000..36ab457 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp @@ -0,0 +1,127 @@ +#include "viewmodifier.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include "schemaresolver.h" +#include "selectresolver.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "common/unused.h" + +ViewModifier::ViewModifier(Db* db, const QString& view) : + ViewModifier(db, "main", view) +{ +} + +ViewModifier::ViewModifier(Db* db, const QString& database, const QString& view) : + db(db), database(database), view(view) +{ + dialect = db->getDialect(); +} + +void ViewModifier::alterView(const QString& newView) +{ + Parser parser(dialect); + if (!parser.parse(newView) || parser.getQueries().size() == 0) + { + errors << QObject::tr("Could not parse DDL of the view to be created. Details: %1").arg(parser.getErrorString()); + return; + } + + SqliteQueryPtr query = parser.getQueries().first(); + createView = query.dynamicCast(); + + if (!createView) + { + errors << QObject::tr("Parsed query is not CREATE VIEW. It's: %1").arg(sqliteQueryTypeToString(query->queryType)); + return; + } + + alterView(createView); +} + +void ViewModifier::alterView(SqliteCreateViewPtr newView) +{ + createView = newView; + + addMandatorySql(QString("DROP VIEW %1").arg(wrapObjIfNeeded(view, dialect))); + addMandatorySql(newView->detokenize()); + + collectNewColumns(); + handleTriggers(); + + // TODO handle other views selecting from this view +} + +void ViewModifier::handleTriggers() +{ + SchemaResolver resolver(db); + QList triggers = resolver.getParsedTriggersForView(view, true); + foreach (SqliteCreateTriggerPtr trigger, triggers) + { + addOptionalSql(QString("DROP TRIGGER %1").arg(wrapObjIfNeeded(trigger->trigger, dialect))); + + if (!handleNewColumns(trigger)) + continue; + + addOptionalSql(trigger->detokenize()); + } +} + +bool ViewModifier::handleNewColumns(SqliteCreateTriggerPtr trigger) +{ + UNUSED(trigger); + // TODO update all occurances of columns in "UPDATE OF" and statements inside, just like it would be done in TableModifier. + return true; +} + +void ViewModifier::collectNewColumns() +{ + SelectResolver resolver(db, createView->select->detokenize()); + QList > multiColumns = resolver.resolve(createView->select); + if (multiColumns.size() < 1) + { + warnings << QObject::tr("SQLiteStudio was unable to resolve columns returned by the new view, " + "therefore it won't be able to tell which triggers might fail during the recreation process."); + return; + } + + foreach (const SelectResolver::Column& col, multiColumns.first()) + newColumns << col.column; +} + +void ViewModifier::addMandatorySql(const QString& sql) +{ + sqls << sql; + sqlMandatoryFlags << true; +} + +void ViewModifier::addOptionalSql(const QString& sql) +{ + + sqls << sql; + sqlMandatoryFlags << false; +} + +QStringList ViewModifier::generateSqls() const +{ + return sqls; +} + +QList ViewModifier::getMandatoryFlags() const +{ + return sqlMandatoryFlags; +} + +QStringList ViewModifier::getWarnings() const +{ + return warnings; +} + +QStringList ViewModifier::getErrors() const +{ + return errors; +} + +bool ViewModifier::hasMessages() const +{ + return errors.size() > 0 || warnings.size() > 0; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/viewmodifier.h b/SQLiteStudio3/coreSQLiteStudio/viewmodifier.h new file mode 100644 index 0000000..6d7c770 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/viewmodifier.h @@ -0,0 +1,53 @@ +#ifndef VIEWMODIFIER_H +#define VIEWMODIFIER_H + +#include "db/db.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include + +class API_EXPORT ViewModifier +{ + public: + ViewModifier(Db* db, const QString& view); + ViewModifier(Db* db, const QString& database, const QString& view); + + void alterView(const QString& newView); + void alterView(SqliteCreateViewPtr newView); + + QStringList generateSqls() const; + QList getMandatoryFlags() const; + QStringList getWarnings() const; + QStringList getErrors() const; + bool hasMessages() const; + + private: + void handleTriggers(); + void collectNewColumns(); + void addMandatorySql(const QString& sql); + void addOptionalSql(const QString& sql); + bool handleNewColumns(SqliteCreateTriggerPtr trigger); + + Db* db = nullptr; + Dialect dialect; + QString database; + QString view; + + /** + * @brief sqls Statements to be executed to make changes real. + */ + QStringList sqls; + QList sqlMandatoryFlags; + + QStringList warnings; + QStringList errors; + + /** + * @brief createView Original DDL. + */ + SqliteCreateViewPtr createView; + + QStringList newColumns; +}; + +#endif // VIEWMODIFIER_H diff --git a/SQLiteStudio3/create_linux_portable.sh b/SQLiteStudio3/create_linux_portable.sh new file mode 100644 index 0000000..8b7b8fe --- /dev/null +++ b/SQLiteStudio3/create_linux_portable.sh @@ -0,0 +1,152 @@ +#!/bin/sh + +printUsage() { + echo "$0 [tgz|dist]" +} + +if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then + printUsage + exit 1 +fi + +if [ "$#" -eq 3 ] && [ "$3" != "tgz" ] && [ "$3" != "dist" ]; then + printUsage + exit 1 +fi + +which chrpath >/dev/null +if [ "$?" -ne 0 ]; then + echo "chrpath program missing!" + exit 1 +fi + +qt_paths_bin="${2/qmake/qtpaths}" +$qt_paths_bin -v >/dev/null 2>&1 +if [ "$?" -ne 0 ]; then + echo "qtpaths program missing!" + exit 1 +fi + +cd $1 + +required_modules="libQt5Core.so libQt5Concurrent.so libQt5DBus.so libQt5Gui.so libQt5Network.so libQt5PrintSupport.so libQt5Script.so libQt5Widgets.so libQt5Xml.so \ + libQt5Svg.so" +required_plugins="platforms/libqxcb.so imageformats/libqgif.so imageformats/libqicns.so imageformats/libqico.so imageformats/libqjpeg.so imageformats/libqmng.so \ + imageformats/libqsvg.so imageformats/libqtga.so imageformats/libqtiff.so iconengines/libqsvgicon.so printsupport/libcupsprintersupport.so platformthemes/libqgtk2.so" + +qt_lib_dir=`ldd SQLiteStudio/sqlitestudio | grep libQt5Core | awk '{print $3;}'` +qt_lib_dir=`dirname $qt_lib_dir` +qt_plugins_dir=`$qt_paths_bin --plugin-dir` + +# Create portable dir to store distribution in +rm -rf portable +mkdir portable +cd portable +portable=`pwd` + +# Copy all output from compilation here +cp -R $1/SQLiteStudio . + +# Make lib directory to move all *.so files (sqlitestudio files and Qt files and dependencies) +cd SQLiteStudio + +# Copy SQLite libs +cd $portable/SQLiteStudio +sqlite3_lib=`ldd $1/SQLiteStudio/lib/libcoreSQLiteStudio.so | grep libsqlite | awk '{print $3;}'` +sqlite2_lib=`ldd plugins/libDbSqlite2.so | grep libsqlite | awk '{print $3;}'` +cp $sqlite3_lib lib +cp $sqlite2_lib lib +strip lib/*libsqlite* + +# Copy Qt +cd $portable/SQLiteStudio/lib +for module in $required_modules; do + if [ ! -f $qt_lib_dir/$module ]; then + echo "Required Qt module doesn't exist: $qt_lib_dir/$module" + exit 1 + fi + cp -P $qt_lib_dir/$module* . + + for dep_lib in `ldd $qt_lib_dir/$module | grep $qt_lib_dir | awk '{print $3;}'`; do + cp -Pu $dep_lib* . + done +done + +for lib in `ls *.so`; do + chrpath -r \$ORIGIN/lib $lib >/dev/null +done + +# Now copy Qt plugins +cd $portable/SQLiteStudio +qt_plugin_dirs=() +for plugin in $required_plugins; do + if [ ! -f $qt_plugins_dir/$plugin ]; then + echo "Required Qt plugin doesn't exist: $qt_plugins_dir/$plugin" + exit 1 + fi + parts=(${plugin/\// }) + mkdir ${parts[0]} 2>/dev/null + cp -P $qt_plugins_dir/$plugin ${parts[0]} + + # Update rpath in Qt plugins + cd ${parts[0]} + for lib in `ls *.so`; do + chrpath -r \$ORIGIN/../lib $lib >/dev/null + done + cd .. +done + +cd $portable/SQLiteStudio +chrpath -r \$ORIGIN/lib sqlitestudio >/dev/null +chrpath -r \$ORIGIN/lib sqlitestudiocli >/dev/null + +cd $portable +VERSION=`SQLiteStudio/sqlitestudiocli -v | awk '{print $2}'` + +if [ "$3" == "tgz" ]; then + tar cf sqlitestudio-$VERSION.tar SQLiteStudio + xz -z sqlitestudio-$VERSION.tar +elif [ "$3" == "dist" ]; then + # Complete + echo "Building complete package: sqlitestudio-$VERSION.tar.xz" + tar cf sqlitestudio-$VERSION.tar SQLiteStudio + xz -z sqlitestudio-$VERSION.tar + + # App + echo "Building incremental update package: sqlitestudio-$VERSION.tar.gz" + cp -R SQLiteStudio app + cd app + rm -rf plugins + rm -f lib/libQ* + rm -rf iconengines + rm -rf imageformats + rm -rf platforms + rm -rf platformthemes + rm -rf printsupport + rm -f lib/libicu* + rm -f lib/libsqlite.so.0 ;# this is for SQLite 2 + find . -type l -exec rm -f {} \; + tar cf sqlitestudio-$VERSION.tar * + gzip -9 sqlitestudio-$VERSION.tar + mv sqlitestudio-$VERSION.tar.gz .. + cd .. + rm -rf app + + # Plugins + mkdir plugins + SQLiteStudio/sqlitestudio --list-plugins | while read line + do + PLUGIN=`echo $line | awk '{print $1}'` + PLUGIN_VER=`echo $line | awk '{print $2}'` + if [ -f SQLiteStudio/plugins/lib$PLUGIN.so ]; then + echo "Building plugin package: $PLUGIN-$PLUGIN_VER.tar.gz" + cp SQLiteStudio/plugins/lib$PLUGIN.so plugins/ + tar cf $PLUGIN\-$PLUGIN_VER.tar plugins + gzip -9 $PLUGIN\-$PLUGIN_VER.tar + fi + rm -f plugins/* + done + rm -rf plugins + + echo "Done." +fi diff --git a/SQLiteStudio3/create_macosx_bundle.sh b/SQLiteStudio3/create_macosx_bundle.sh new file mode 100755 index 0000000..eb7d537 --- /dev/null +++ b/SQLiteStudio3/create_macosx_bundle.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +printUsage() { + echo "$0 [dmg|dist]" +} + +if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then + printUsage + exit 1 +fi + +if [ "$#" -eq 3 ] && [ "$3" != "dmg" ] && [ "$3" != "dist" ]; then + printUsage + exit 1 +fi + +qt_deploy_bin="${2/qmake/macdeployqt}" +$qt_deploy_bin -v >/dev/null 2>&1 +if [ "$?" -ne 0 ]; then + echo "macdeployqt program missing!" + exit 1 +fi + +cd $1/SQLiteStudio + +rm -rf SQLiteStudio.app/Contents/Frameworks +rm -rf SQLiteStudio.app/Contents/PlugIns +rm -f SQLiteStudio.app/Contents/MacOS/sqlitestudiocli +rm -f SQLiteStudio.app/Contents/Resources/qt.conf + +mkdir SQLiteStudio.app/Contents/Frameworks + +cp -RP plugins SQLiteStudio.app/Contents +mv SQLiteStudio.app/Contents/plugins SQLiteStudio.app/Contents/PlugIns + +cp -RP lib*SQLiteStudio*.dylib SQLiteStudio.app/Contents/Frameworks + +qtcore_path=`otool -L sqlitestudiocli | grep QtCore | awk '{print $1;}'` +new_qtcore_path="@loader_path/../Frameworks/QtCore.framework/Versions/5/QtCore" + +cp -P sqlitestudiocli SQLiteStudio.app/Contents/MacOS +install_name_tool -change libcoreSQLiteStudio.1.dylib "@loader_path/../Frameworks/libcoreSQLiteStudio.1.dylib" SQLiteStudio.app/Contents/MacOS/sqlitestudiocli +install_name_tool -change $qtcore_path $new_qtcore_path SQLiteStudio.app/Contents/MacOS/sqlitestudiocli + +cp -RP ../../../lib/*.dylib SQLiteStudio.app/Contents/Frameworks + +if [ "$3" == "dmg" ]; then + $qt_deploy_bin SQLiteStudio.app -dmg +elif [ "$3" == "dist" ]; then + $qt_deploy_bin SQLiteStudio.app -dmg + + cd $1/SQLiteStudio + VERSION=`SQLiteStudio.app/Contents/MacOS/sqlitestudiocli -v | awk '{print $2}'` + + mv SQLiteStudio.dmg sqlitestudio-$VERSION.dmg + + # App + echo "Building incremental update package: sqlitestudio-$VERSION.zip" + cp -R SQLiteStudio.app app + cd app/Contents + rm -rf PlugIns + rm -rf Frameworks/Qt*.framework + find Frameworks -type l -exec rm -f {} \; + cd .. + zip -r sqlitestudio-$VERSION.zip * + mv sqlitestudio-$VERSION.zip .. + cd .. + rm -rf app + + # Plugins + mkdir Contents Contents/PlugIns + SQLiteStudio.app/Contents/MacOS/sqlitestudio --list-plugins | while read line + do + PLUGIN=`echo $line | awk '{print $1}'` + PLUGIN_VER=`echo $line | awk '{print $2}'` + if [ -f SQLiteStudio.app/Contents/PlugIns/lib$PLUGIN.dylib ]; then + echo "Building plugin package: $PLUGIN-$PLUGIN_VER.tar.gz" + cp SQLiteStudio.app/Contents/PlugIns/lib$PLUGIN.dylib Contents/PlugIns + zip -r $PLUGIN\-$PLUGIN_VER.zip Contents + fi + rm -f Contents/PlugIns/* + done + rm -rf Contents + + echo "Done." +else + $qt_deploy_bin SQLiteStudio.app +fi diff --git a/SQLiteStudio3/create_source_dist.sh b/SQLiteStudio3/create_source_dist.sh new file mode 100755 index 0000000..646e493 --- /dev/null +++ b/SQLiteStudio3/create_source_dist.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +OLDDIR=`pwd` + +TEMP=`mktemp -d` +cd $TEMP + +svn co svn://sqlitestudio.pl/sqlitestudio3/trunk sqlitestudio + +cd sqlitestudio +rm -rf .svn + +VERSION_INT=`cat SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp | grep static | grep sqlitestudioVersion | sed 's/\;//'` +VERSION=`echo $VERSION_INT | awk '{print int($6/10000) "." int($6/100%100) "." int($6%100)}'` + +tar cf ../sqlitestudio-$VERSION.tar SQLiteStudio3 Plugins +gzip -9 ../sqlitestudio-$VERSION.tar + +zip -r ../sqlitestudio-$VERSION.zip SQLiteStudio3 Plugins + +cd $OLDDIR + +mv $TEMP/sqlitestudio-$VERSION.zip ../output +mv $TEMP/sqlitestudio-$VERSION.tar.gz ../output + +cd ../output + +rm -rf $TEMP + +echo "Source packages stored in `pwd`" + +cd $OLDDIR diff --git a/SQLiteStudio3/create_win32_portable.bat b/SQLiteStudio3/create_win32_portable.bat new file mode 100644 index 0000000..daf89d6 --- /dev/null +++ b/SQLiteStudio3/create_win32_portable.bat @@ -0,0 +1,119 @@ +@echo off + +set QT_DIR=c:\Qt\5.3\mingw482_32\bin +set ZIP="c:\Program Files (x86)\7-Zip\7z.exe" + +set QMAKE=%QT_DIR%\qmake.exe +set OLDDIR=%CD% + +rem Find Qt +if exist %QMAKE% ( + echo Qt found at %QT_DIR% +) else ( + echo Cannot find Qt + GOTO:EOF +) + +rem Find 7zip +if exist %ZIP% ( + echo 7zip found at %ZIP% +) else ( + echo Cannot find 7zip + GOTO:EOF +) + +cd %OLDDIR% + +rem Clean up +echo Cleaning up... +cd ..\output +rmdir /s /q portable + +rem Create a copy +echo Creating a portable distribution +mkdir portable\SQLiteStudio +xcopy SQLiteStudio portable\SQLiteStudio /s /e /q > nul + +rem Remove .a files from app dir +cd portable\SQLiteStudio +del /q *.a +set PORTABLE=%CD% + +rem Remove .a files from plugins dir +cd plugins +del /q *.a +rem Copy Qt files +cd %QT_DIR% +set QT_LIB_LIST=Qt5Core Qt5Gui Qt5Network Qt5PrintSupport Qt5Script Qt5Svg Qt5Widgets Qt5Xml icudt52 icuin52 icuuc52 libgcc_s_dw2-1 libstdc++-6 libwinpthread-1 +for %%i in (%QT_LIB_LIST%) do ( + copy "%%i.dll" %PORTABLE% > nul +) + +mkdir %PORTABLE%\iconengines %PORTABLE%\imageformats %PORTABLE%\platforms %PORTABLE%\printsupport +cd %QT_DIR%\..\plugins + +copy iconengines\qsvgicon.dll %PORTABLE%\iconengines > nul +copy platforms\qwindows.dll %PORTABLE%\platforms > nul +copy printsupport\windowsprintersupport.dll %PORTABLE%\printsupport > nul +for %%i in (qdds qgif qicns qico qjpeg qsvg qtga qtiff qwbmp) do ( + copy imageformats\%%i.dll %PORTABLE%\imageformats > nul +) + +rem Copy app-specific deps +cd %OLDDIR%\..\..\lib +copy *.dll %PORTABLE% > nul + +call:getAppVersion +cd %PORTABLE%\.. +%ZIP% a -r sqlitestudio-%APP_VERSION%.zip SQLiteStudio > nul + +rem Incremental package +echo Creating incremental update package +cd %PORTABLE%\.. +mkdir incremental\SQLiteStudio +xcopy SQLiteStudio incremental\SQLiteStudio /s /e /q > nul +cd incremental\SQLiteStudio +del /q Qt5*.dll +del /q icu*.dll +del /q libgcc* libstdc* libwinpthread* +rmdir /s /q iconengines imageformats platforms printsupport plugins + +cd %PORTABLE%\..\incremental +%ZIP% a -r sqlitestudio-%APP_VERSION%.zip SQLiteStudio > nul + +rem Plugin packages +echo Creating plugin updates +cd %PORTABLE%\.. +for /f "delims=" %%p in ('SQLiteStudio\SQLiteStudio.exe --list-plugins') do ( + call:preparePlugin %%p +) + +cd %OLDDIR% +GOTO:EOF + +:preparePlugin + set plugin=%~1 + set plugin_ver=%~2 + if exist SQLiteStudio\plugins\%plugin%.dll ( + echo Creating plugin update: %plugin% + mkdir plugins\%plugin%\SQLiteStudio\plugins + copy SQLiteStudio\plugins\%plugin%.dll plugins\%plugin%\SQLiteStudio\plugins > nul + + cd plugins\%plugin% + %ZIP% a -r ..\%plugin%-%plugin_ver%.zip SQLiteStudio > nul + cd ..\.. + ) +GOTO:EOF + +:getAppVersion + pushd + cd %PORTABLE% + for /f "delims=" %%v in ('sqlitestudiocli --version') do ( + call:getAppVersionFromSecondArgument %%v + ) + popd +GOTO:EOF + +:getAppVersionFromSecondArgument + set APP_VERSION=%~2 +GOTO:EOF diff --git a/SQLiteStudio3/dirs.pri b/SQLiteStudio3/dirs.pri new file mode 100644 index 0000000..be8479e --- /dev/null +++ b/SQLiteStudio3/dirs.pri @@ -0,0 +1,37 @@ +DESTDIR = $$PWD/../output/SQLiteStudio +OBJECTS_DIR = $$PWD/../output/build +MOC_DIR = $$PWD/../output/build +UI_DIR = $$PWD/../output/build + +LIBS += -L$$DESTDIR + + +macx: { + QMAKE_CXXFLAGS += -Wno-gnu-zero-variadic-macro-arguments -Wno-overloaded-virtual + INCLUDEPATH += $$PWD/../../include + LIBS += -L$$PWD/../../lib +} + +win32: { + INCLUDEPATH += $$PWD/../../include $$PWD/../../include/quazip + LIBS += -L$$PWD/../../lib +} + +INCLUDEPATH += $$PWD/coreSQLiteStudio +DEPENDPATH += $$PWD/coreSQLiteStudio + +contains(QT, gui): { + INCLUDEPATH += $$PWD/guiSQLiteStudio $$PWD/../output/build/guiSQLiteStudio + DEPENDPATH += $$PWD/guiSQLiteStudio +} + +win32|macx: { + CONFIG += portable +} + +portable { + QMAKE_LFLAGS += -Wl,-rpath,. + linux: { + LIBS += -L$$DESTDIR/lib + } +} diff --git a/SQLiteStudio3/docs/sqlitestudio3_docs.cfg b/SQLiteStudio3/docs/sqlitestudio3_docs.cfg new file mode 100644 index 0000000..0b2d243 --- /dev/null +++ b/SQLiteStudio3/docs/sqlitestudio3_docs.cfg @@ -0,0 +1,2280 @@ +# Doxyfile 1.8.5 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "SQLiteStudio" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "C++ API documentation" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = sqlitestudio_logo.png + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = sqlitestudio3 + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese- +# Traditional, Croatian, Czech, Danish, Dutch, English, Esperanto, Farsi, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en, +# Korean, Korean-en, Latvian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, +# Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = .. + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = *.cpp, *.h + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /
%1
"; + static const QString rowTmp = "%1%2"; + static const QString hdrRowTmp = "%2 %3"; + static const QString constrRowTmp = "%2%3"; + static const QString emptyRow = ""; + + if (!index().isValid()) + return QString::null; + + SqlQueryModelColumn* col = getColumn(); + if (!col) + return QString::null; // happens when simple execution method was performed + + QStringList rows; + rows << hdrRowTmp.arg(ICONS.COLUMN.getPath()).arg(tr("Column:", "data view tooltip")).arg(col->column); + rows << rowTmp.arg(tr("Data type:", "data view")).arg(col->dataType.toString()); + if (!col->table.isNull()) + { + rows << rowTmp.arg(tr("Table:", "data view tooltip")).arg(col->table); + + RowId rowId = getRowId(); + QString rowIdStr; + if (rowId.size() == 1) + { + rowIdStr = rowId.values().first().toString(); + } + else + { + QStringList values; + QString rowIdValue; + QHashIterator it(rowId); + while (it.hasNext()) + { + it.next(); + rowIdValue = it.value().toString(); + if (rowIdValue.length() > 30) + rowIdValue = rowIdValue.left(27) + "..."; + + values << it.key() + "=" + rowIdValue; + } + rowIdStr = "[" + values.join(", ") + "]"; + } + rows << rowTmp.arg("ROWID:").arg(rowIdStr); + } + + if (col->constraints.size() > 0) + { + rows << emptyRow; + rows << hdrRowTmp.arg(ICONS.COLUMN_CONSTRAINT.getPath()).arg(tr("Constraints:", "data view tooltip")).arg(""); + foreach (SqlQueryModelColumn::Constraint* constr, col->constraints) + rows << constrRowTmp.arg(constr->getIcon()->toUrl()).arg(constr->getTypeString()).arg(constr->getDetails()); + } + + return tableTmp.arg(rows.join("")); +} + +SqlQueryModelColumn* SqlQueryItem::getColumn() const +{ + return QStandardItem::data(DataRole::COLUMN).value(); +} + +void SqlQueryItem::setColumn(SqlQueryModelColumn* column) +{ + QStandardItem::setData(QVariant::fromValue(column), DataRole::COLUMN); +} + +SqlQueryModel *SqlQueryItem::getModel() const +{ + if (!model()) + return nullptr; + + return dynamic_cast(model()); +} + +void SqlQueryItem::setData(const QVariant &value, int role) +{ + switch (role) + { + case Qt::EditRole: + { + // -1 column is used by Qt for header items (ie. when setHeaderData() is called) + // and we want this to mean that the value was loaded from db, because it forces + // the value to be interpreted as not modified. + setValue(value, false, (column() == -1)); + return; + } + } + + QStandardItem::setData(value, role); +} + +QVariant SqlQueryItem::data(int role) const +{ + switch (role) + { + case Qt::EditRole: + { + if (isDeletedRow()) + return QVariant(); + + return getValue(); + } + case Qt::DisplayRole: + { + if (isDeletedRow()) + return ""; + + QVariant value = getValueForDisplay(); + if (value.isNull()) + return "NULL"; + + return value; + } + case Qt::ForegroundRole: + { + QVariant value = getValue(); + if (value.isNull()) + return QBrush(CFG_UI.Colors.DataNullFg.get()); + + break; + } + case Qt::BackgroundRole: + { + if (isDeletedRow()) + return QBrush(CFG_UI.Colors.DataDeletedBg.get()); + + break; + } + case Qt::TextAlignmentRole: + { + QVariant value = getValue(); + if (value.isNull() || isDeletedRow()) + return Qt::AlignCenter; + + break; + } + case Qt::FontRole: + { + QFont font = CFG_UI.Fonts.DataView.get(); + + QVariant value = getValue(); + if (value.isNull() || isDeletedRow()) + font.setItalic(true); + + return font; + } + case Qt::ToolTipRole: + { + return getToolTip(); + } + } + + return QStandardItem::data(role); +} + +QString SqlQueryItem::loadFullData() +{ + SqlQueryModelColumn* col = getColumn(); + if (col->editionForbiddenReason.size() > 0) + { + qWarning() << "Tried to load full cell which is not editable. This should be already handled in Editor class when invoking edition action."; + return tr("This cell is not editable, because: %1").arg(SqlQueryModelColumn::resolveMessage(col->editionForbiddenReason.values().first())); + } + + if (isJustInsertedWithOutRowId()) + { + QString msg = tr("When inserted new row to the WITHOUT ROWID table, using DEFAULT value for PRIMARY KEY, " + "the table has to be reloaded in order to edit the new row."); + return tr("This cell is not editable, because: %1").arg(msg); + } + + SqlQueryModel *model = getModel(); + Db* db = model->getDb(); + if (!db->isOpen()) + { + qWarning() << "Tried to load the data for a cell that refers to the already closed database."; + return tr("Cannot load the data for a cell that refers to the already closed database."); + } + + Dialect dialect = db->getDialect(); + + // Query + RowIdConditionBuilder rowIdBuilder; + rowIdBuilder.setRowId(getRowId()); + QString query = "SELECT %1 FROM %2 WHERE " + rowIdBuilder.build(); + + // Column + query = query.arg(wrapObjIfNeeded(col->column, dialect)); + + // Database and table + QString source = wrapObjIfNeeded(col->table, dialect); + if (!col->database.isNull()) + source.prepend(wrapObjIfNeeded(col->database, dialect)+"."); + + query = query.arg(source); + + // Get the data + SqlQueryPtr results = db->exec(query, rowIdBuilder.getQueryArgs()); + if (results->isError()) + return results->getErrorText(); + + setValue(results->getSingleCell(), false, true); + return QString::null; +} + +QVariant SqlQueryItem::getFullValue() +{ + if (!isLimitedValue()) + return getValue(); + + QVariant originalValue = getValue(); + loadFullData(); + QVariant result = getValue(); + setValue(originalValue, true, !isUncommited()); + return result; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h new file mode 100644 index 0000000..b2552cd --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h @@ -0,0 +1,96 @@ +#ifndef SQLQUERYITEM_H +#define SQLQUERYITEM_H + +#include "sqlquerymodelcolumn.h" +#include "db/sqlquery.h" +#include "guiSQLiteStudio_global.h" +#include + +class SqlQueryModel; + +class GUI_API_EXPORT SqlQueryItem : public QObject, public QStandardItem +{ + Q_OBJECT + + public: + struct GUI_API_EXPORT DataRole // not 'enum class' because we need autocasting to int for this one + { + enum Enum + { + ROWID = 1001, + VALUE = 1002, + LIMITED_VALUE = 1003, + COLUMN = 1004, + UNCOMMITED = 1005, + COMMITING_ERROR = 1006, + NEW_ROW = 1007, + DELETED = 1008, + OLD_VALUE = 1009, + JUST_INSERTED_WITHOUT_ROWID = 1010, + VALUE_FOR_DISPLAY = 1011 + }; + }; + + explicit SqlQueryItem(QObject *parent = 0); + SqlQueryItem(const SqlQueryItem& item); + + QStandardItem* clone() const; + + RowId getRowId() const; + void setRowId(const RowId& rowId); + + bool isUncommited() const; + void setUncommited(bool uncommited); + void rollback(); + + bool isCommitingError() const; + void setCommitingError(bool isError); + + bool isNewRow() const; + void setNewRow(bool isNew); + + bool isJustInsertedWithOutRowId() const; + void setJustInsertedWithOutRowId(bool justInsertedWithOutRowId); + + bool isDeletedRow() const; + void setDeletedRow(bool isDeleted); + + QVariant getValue() const; + void setValue(const QVariant& value, bool limited = false, bool loadedFromDb = false); + bool isLimitedValue() const; + + QVariant getOldValue() const; + void setOldValue(const QVariant& value); + + QVariant getValueForDisplay() const; + void setValueForDisplay(const QVariant& value); + + /** + * @brief loadFullData Reloads entire value of the cell from database. + * @return QString::null on sucess, or error string on failure. + */ + QString loadFullData(); + + /** + * @brief getFullValue Loads and returns full value from database, but keeps the original value. + * @return Full value, reloaded from database. + * Calls loadFullData(), then getValue() for the result, + * but just before returning - restores initial, limited value. + */ + QVariant getFullValue(); + + SqlQueryModelColumn* getColumn() const; + void setColumn(SqlQueryModelColumn* column); + + SqlQueryModel* getModel() const; + + void setData(const QVariant& value, int role = Qt::UserRole + 1); + QVariant data(int role = Qt::UserRole + 1) const; + + private: + void setLimitedValue(bool limited); + QVariant adjustVariantType(const QVariant& value); + QString getToolTip() const; +}; + +#endif // SQLQUERYITEM_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp new file mode 100644 index 0000000..ab8f7f2 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp @@ -0,0 +1,70 @@ +#include "sqlqueryitemdelegate.h" +#include "sqlquerymodel.h" +#include "sqlqueryitem.h" +#include "common/unused.h" +#include "services/notifymanager.h" +#include "uiconfig.h" +#include +#include +#include + +SqlQueryItemDelegate::SqlQueryItemDelegate(QObject *parent) : + QStyledItemDelegate(parent) +{ +} + +void SqlQueryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + + SqlQueryItem* item = getItem(index); + + if (item->isUncommited()) + { + painter->setPen(item->isCommitingError() ? CFG_UI.Colors.DataUncommitedError.get() : CFG_UI.Colors.DataUncommited.get()); + painter->setBrush(Qt::NoBrush); + painter->drawRect(option.rect.x(), option.rect.y(), option.rect.width()-1, option.rect.height()-1); + } +} + +QWidget* SqlQueryItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + UNUSED(option); + if (!index.isValid()) + return nullptr; + + const SqlQueryModel* model = dynamic_cast(index.model()); + SqlQueryItem* item = model->itemFromIndex(index); + + if (item->isDeletedRow()) + { + notifyWarn(tr("Cannot edit this cell. Details: %2").arg(tr("The row is marked for deletion."))); + return nullptr; + } + + if (!item->getColumn()->canEdit()) + { + notifyWarn(tr("Cannot edit this cell. Details: %2").arg(item->getColumn()->getEditionForbiddenReason())); + return nullptr; + } + + if (item->isLimitedValue()) + item->loadFullData(); + + return getEditor(item->getValue().userType(), parent); +} + +SqlQueryItem* SqlQueryItemDelegate::getItem(const QModelIndex &index) const +{ + const SqlQueryModel* queryModel = dynamic_cast(index.model()); + return queryModel->itemFromIndex(index); +} + +QWidget* SqlQueryItemDelegate::getEditor(int type, QWidget* parent) const +{ + UNUSED(type); + QLineEdit *editor = new QLineEdit(parent); + editor->setFrame(editor->style()->styleHint(QStyle::SH_ItemView_DrawDelegateFrame, 0, editor)); + return editor; + +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h new file mode 100644 index 0000000..a190202 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h @@ -0,0 +1,23 @@ +#ifndef SQLQUERYITEMDELEGATE_H +#define SQLQUERYITEMDELEGATE_H + +#include "guiSQLiteStudio_global.h" +#include + +class SqlQueryItem; + +class GUI_API_EXPORT SqlQueryItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT + public: + explicit SqlQueryItemDelegate(QObject *parent = 0); + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + private: + SqlQueryItem* getItem(const QModelIndex &index) const; + QWidget* getEditor(int type, QWidget* parent) const; +}; + +#endif // SQLQUERYITEMDELEGATE_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp new file mode 100644 index 0000000..56bf78d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp @@ -0,0 +1,1519 @@ +#include "sqlquerymodel.h" +#include "parser/keywords.h" +#include "sqlqueryitem.h" +#include "services/notifymanager.h" +#include "common/utils_sql.h" +#include "schemaresolver.h" +#include "common/unused.h" +#include "db/sqlerrorcodes.h" +#include "parser/ast/sqlitecreatetable.h" +#include "uiconfig.h" +#include "datagrid/sqlqueryview.h" +#include "datagrid/sqlqueryrownummodel.h" +#include +#include +#include +#include +#include +#include +#include + +SqlQueryModel::SqlQueryModel(QObject *parent) : + QStandardItemModel(parent) +{ + queryExecutor = new QueryExecutor(); + queryExecutor->setDataLengthLimit(cellDataLengthLimit); + connect(queryExecutor, SIGNAL(executionFinished(SqlQueryPtr)), this, SLOT(handleExecFinished(SqlQueryPtr))); + connect(queryExecutor, SIGNAL(executionFailed(int,QString)), this, SLOT(handleExecFailed(int,QString))); + connect(queryExecutor, SIGNAL(resultsCountingFinished(quint64,quint64,int)), this, SLOT(resultsCountingFinished(quint64,quint64,int))); + setItemPrototype(new SqlQueryItem()); +} + +SqlQueryModel::~SqlQueryModel() +{ + delete queryExecutor; + queryExecutor = nullptr; +} + +void SqlQueryModel::staticInit() +{ +} + +QString SqlQueryModel::getQuery() const +{ + return query; +} + +void SqlQueryModel::setQuery(const QString &value) +{ + query = value; +} + +void SqlQueryModel::setExplainMode(bool explain) +{ + this->explain = explain; +} + +void SqlQueryModel::executeQuery() +{ + if (queryExecutor->isExecutionInProgress()) + { + notifyWarn(tr("Only one query can be executed simultaneously.")); + return; + } + + sortOrder.clear(); + queryExecutor->setSkipRowCounting(false); + queryExecutor->setSortOrder(sortOrder); + queryExecutor->setPage(0); + reloading = false; + + executeQueryInternal(); +} + +void SqlQueryModel::executeQueryInternal() +{ + if (!db || !db->isValid()) + { + notifyWarn("Cannot execute query on undefined or invalid database."); + internalExecutionStopped(); + return; + } + + if (query.isEmpty()) + { + notifyWarn("Cannot execute empty query."); + internalExecutionStopped(); + return; + } + + QList uncommitedItems = getUncommitedItems(); + if (uncommitedItems.size() > 0) + { + QMessageBox::StandardButton result = QMessageBox::question(nullptr, tr("Uncommited data"), + tr("There are uncommited data changes. Do you want to proceed anyway? " + "All uncommited changes will be lost.")); + + if (result != QMessageBox::Yes) + { + internalExecutionStopped(); + return; + } + + rollback(uncommitedItems); + } + + emit executionStarted(); + + queryExecutor->setQuery(query); + queryExecutor->setResultsPerPage(CFG_UI.General.NumberOfRowsPerPage.get()); + queryExecutor->setExplainMode(explain); + queryExecutor->setPreloadResults(true); + queryExecutor->exec(); +} + +void SqlQueryModel::internalExecutionStopped() +{ + reloading = false; + emit loadingEnded(false); +} + +void SqlQueryModel::interrupt() +{ + queryExecutor->interrupt(); +} + +qint64 SqlQueryModel::getExecutionTime() +{ + return lastExecutionTime; +} + +qint64 SqlQueryModel::getTotalRowsReturned() +{ + return totalRowsReturned; +} + +qint64 SqlQueryModel::getTotalRowsAffected() +{ + return rowsAffected; +} + +qint64 SqlQueryModel::getTotalPages() +{ + return totalPages; +} + +QList SqlQueryModel::getColumns() +{ + return columns; +} + +SqlQueryItem *SqlQueryModel::itemFromIndex(const QModelIndex &index) const +{ + return dynamic_cast(QStandardItemModel::itemFromIndex(index)); +} + +SqlQueryItem*SqlQueryModel::itemFromIndex(int row, int column) const +{ + return dynamic_cast(item(row, column)); +} + +int SqlQueryModel::getCellDataLengthLimit() +{ + return cellDataLengthLimit; +} + +QModelIndexList SqlQueryModel::findIndexes(int role, const QVariant& value, int hits) const +{ + QModelIndex startIdx = index(0, 0); + QModelIndex endIdx = index(rowCount() - 1, columnCount() - 1); + return findIndexes(startIdx, endIdx, role, value, hits); +} + +QModelIndexList SqlQueryModel::findIndexes(const QModelIndex& start, const QModelIndex& end, int role, const QVariant& value, int hits) const +{ + QModelIndexList results; + bool allHits = hits < 0; + QModelIndex parentIdx = parent(start); + int fromRow = start.row(); + int toRow = end.row(); + int fromCol = start.column(); + int toCol = end.column(); + + for (int row = fromRow; row <= toRow && (allHits || results.count() < hits); row++) + { + for (int col = fromCol; col <= toCol && (allHits || results.count() < hits); col++) + { + QModelIndex idx = index(row, col, parentIdx); + if (!idx.isValid()) + continue; + + QVariant cellVal = data(idx, role); + if (value != cellVal) + continue; + + results.append(idx); + } + } + + return results; +} + +QList SqlQueryModel::findItems(int role, const QVariant& value, int hits) const +{ + return toItemList(findIndexes(role, value, hits)); +} + +QList SqlQueryModel::findItems(const QModelIndex& start, const QModelIndex& end, int role, const QVariant& value, int hits) const +{ + return toItemList(findIndexes(start, end, role, value, hits)); +} + +QList SqlQueryModel::getUncommitedItems() const +{ + return findItems(SqlQueryItem::DataRole::UNCOMMITED, true); +} + +QList > SqlQueryModel::groupItemsByRows(const QList& items) +{ + QMap> itemsByRow; + for (SqlQueryItem* item : items) + itemsByRow[item->row()] << item; + + return itemsByRow.values(); +} + +QHash> SqlQueryModel::groupItemsByTable(const QList& items) +{ + QHash> itemsByTable; + Table table; + foreach (SqlQueryItem* item, items) + { + if (item->getColumn()) + { + table.setDatabase(item->getColumn()->database.toLower()); + table.setTable(item->getColumn()->table.toLower()); + itemsByTable[table] << item; + } + else + itemsByTable[Table()] << item; + } + + return itemsByTable; +} + +QList SqlQueryModel::filterOutCommitedItems(const QList& items) +{ + // This method doesn't make use of QMutableListIterator to remove items from passed list, + // because it would require list in argument to drop 'const' keyword and it's already + // there in calling methods, so it's easier to copy list and filter on the fly. + QList newList; + foreach (SqlQueryItem* item, items) + if (item->isUncommited()) + newList << item; + + return newList; +} + +QList SqlQueryModel::getRow(int row) +{ + QList items; + for (int i = 0; i < columnCount(); i++) + items << itemFromIndex(row, i); + + return items; +} + +SqlQueryModel::Features SqlQueryModel::features() const +{ + return Features(); +} + +QList SqlQueryModel::toItemList(const QModelIndexList& indexes) const +{ + QList list; + foreach (const QModelIndex& idx, indexes) + list << itemFromIndex(idx); + + return list; +} + +void SqlQueryModel::commit() +{ + QList items = findItems(SqlQueryItem::DataRole::UNCOMMITED, true); + commitInternal(items); +} + +void SqlQueryModel::commit(const QList& items) +{ + commitInternal(filterOutCommitedItems(items)); +} + +bool SqlQueryModel::commitRow(const QList& itemsInRow) +{ + const SqlQueryItem* item = itemsInRow.at(0); + if (!item) + { + qWarning() << "null item while call to commitRow() method. It shouldn't happen."; + return true; + } + if (item->isNewRow()) + return commitAddedRow(getRow(item->row())); // we need to get all items again, in case of selective commit + else if (item->isDeletedRow()) + return commitDeletedRow(getRow(item->row())); // we need to get all items again, in case of selective commit + else + return commitEditedRow(itemsInRow); +} + +void SqlQueryModel::rollbackRow(const QList& itemsInRow) +{ + const SqlQueryItem* item = itemsInRow.at(0); + if (!item) + { + qWarning() << "null item while call to rollbackRow() method. It shouldn't happen."; + return; + } + if (item->isNewRow()) + rollbackAddedRow(getRow(item->row())); // we need to get all items again, in case of selective commit + else if (item->isDeletedRow()) + rollbackDeletedRow(getRow(item->row())); // we need to get all items again, in case of selective commit + else + rollbackEditedRow(itemsInRow); +} + +void SqlQueryModel::rollback() +{ + QList items = findItems(SqlQueryItem::DataRole::UNCOMMITED, true); + rollbackInternal(items); +} + +void SqlQueryModel::rollback(const QList& items) +{ + rollbackInternal(filterOutCommitedItems(items)); +} + +void SqlQueryModel::commitInternal(const QList& items) +{ + Db* db = getDb(); + if (!db->isOpen()) + { + notifyError(tr("Cannot commit the data for a cell that refers to the already closed database.")); + return; + } + + if (!db->begin()) + { + notifyError(tr("Could not begin transaction on the database. Details: %1").arg(db->getErrorText())); + return; + } + + // Removing "commit error" mark from items that are going to be commited now + for (SqlQueryItem* item : items) + item->setCommitingError(false); + + // Grouping by row and commiting + QList > groupedItems = groupItemsByRows(items); + bool ok = true; + foreach (const QList& itemsInRow, groupedItems) + { + if (!commitRow(itemsInRow)) + { + ok = false; + break; + } + } + + // Getting current uncommited list (after rows deletion it may be different) + QList itemsLeft = findItems(SqlQueryItem::DataRole::UNCOMMITED, true); + + // Getting common elements of initial and current item list, because of a possibility of the selective commit + QMutableListIterator it(itemsLeft); + while (it.hasNext()) + { + if (!items.contains(it.next())) + it.remove(); + } + + // Commiting to the database + if (ok) + { + if (!db->commit()) + { + ok = false; + notifyError(tr("An error occurred while commiting the transaction: %1").arg(db->getErrorText())); + } + else + { + // Commited successfully + foreach (SqlQueryItem* item, itemsLeft) + { + item->setUncommited(false); + item->setNewRow(false); + } + + emit commitStatusChanged(getUncommitedItems().size() > 0); + } + } + + if (!ok) + { + if (!db->rollback()) + { + notifyError(tr("An error occurred while rolling back the transaction: %1").arg(db->getErrorText())); + // Nothing else we can do about it, but it should not happen. + } + } +} + +void SqlQueryModel::rollbackInternal(const QList& items) +{ + QList > groupedItems = groupItemsByRows(items); + foreach (const QList& itemsInRow, groupedItems) + rollbackRow(itemsInRow); + + emit commitStatusChanged(getUncommitedItems().size() > 0); +} + +void SqlQueryModel::reload() +{ + queryExecutor->setSkipRowCounting(false); + reloadInternal(); +} + +void SqlQueryModel::reloadInternal() +{ + if (!reloadAvailable) + return; + + if (queryExecutor->isExecutionInProgress()) + { + notifyWarn(tr("Only one query can be executed simultaneously.")); + return; + } + reloading = true; + executeQueryInternal(); +} + +SqlQueryView* SqlQueryModel::getView() const +{ + return view; +} + +void SqlQueryModel::setView(SqlQueryView* value) +{ + view = value; + view->setModel(this); +} + +int SqlQueryModel::getCurrentPage(bool includeOneBeingLoaded) const +{ + int result = includeOneBeingLoaded ? queryExecutor->getPage() : page; + return result < 0 ? 0 : result; +} + +bool SqlQueryModel::commitAddedRow(const QList& itemsInRow) +{ + UNUSED(itemsInRow); + return false; +} + +bool SqlQueryModel::commitEditedRow(const QList& itemsInRow) +{ + if (itemsInRow.size() == 0) + { + qWarning() << "SqlQueryModel::commitEditedRow() called with no items in the list."; + return true; + } + + Dialect dialect = db->getDialect(); + + QHash> itemsByTable = groupItemsByTable(itemsInRow); + + // Values + QString query; + SqlQueryModelColumn* col = nullptr; + QHash queryArgs; + QStringList assignmentArgs; + RowId rowId; + RowId newRowId; + CommitUpdateQueryBuilder queryBuilder; + QHashIterator> it(itemsByTable); + QList items; + Table table; + while (it.hasNext()) + { + it.next(); + table = it.key(); + if (table.getTable().isNull()) + { + qCritical() << "Tried to commit null table in SqlQueryModel::commitEditedRow()."; + continue; + } + + items = it.value(); + if (items.size() == 0) + continue; + + // RowId + queryBuilder.clear(); + rowId = items.first()->getRowId(); + queryBuilder.setRowId(rowId); + newRowId = getNewRowId(rowId, items); // if any of item updates any of rowid columns, then this will be different than initial rowid + + // Database and table + queryBuilder.setTable(wrapObjIfNeeded(table.getTable(), dialect)); + if (!table.getDatabase().isNull()) + queryBuilder.setDatabase(wrapObjIfNeeded(table.getDatabase(), dialect)); + + for (SqlQueryItem* item : items) + { + col = item->getColumn(); + if (col->editionForbiddenReason.size() > 0 || item->isJustInsertedWithOutRowId()) + { + notifyError(tr("Tried to commit a cell which is not editable (yet modified and waiting for commit)! This is a bug. Please report it.")); + return false; + } + + // Column + queryBuilder.addColumn(wrapObjIfNeeded(col->column, dialect)); + } + + // Completing query + query = queryBuilder.build(); + + // RowId condition arguments + queryArgs = queryBuilder.getQueryArgs(); + + // Per-column arguments + assignmentArgs = queryBuilder.getAssignmentArgs(); + for (int i = 0, total = items.size(); i < total; ++i) + queryArgs[assignmentArgs[i]] = items[i]->getValue(); + + // Get the data + SqlQueryPtr results = db->exec(query, queryArgs); + if (results->isError()) + { + for (SqlQueryItem* item : items) + item->setCommitingError(true); + + notifyError(tr("An error occurred while commiting the data: %1").arg(results->getErrorText())); + return false; + } + + // After successful commit, check if RowId was modified and upadate it accordingly + if (rowId != newRowId) + updateRowIdForAllItems(table, rowId, newRowId); + } + + return true; +} + +bool SqlQueryModel::commitDeletedRow(const QList& itemsInRow) +{ + if (itemsInRow.size() == 0) + { + qCritical() << "No items passed to SqlQueryModel::commitDeletedRow()."; + return false; + } + + int row = itemsInRow[0]->index().row(); + return removeRow(row); +} + +void SqlQueryModel::rollbackAddedRow(const QList& itemsInRow) +{ + if (itemsInRow.size() == 0) + { + qCritical() << "No items passed to SqlQueryModel::rollbackAddedRow()."; + return; + } + + int row = itemsInRow[0]->index().row(); + removeRow(row); +} + +void SqlQueryModel::rollbackEditedRow(const QList& itemsInRow) +{ + foreach (SqlQueryItem* item, itemsInRow) + item->rollback(); +} + +void SqlQueryModel::rollbackDeletedRow(const QList& itemsInRow) +{ + foreach (SqlQueryItem* item, itemsInRow) + item->rollback(); +} + +SqlQueryModelColumnPtr SqlQueryModel::getColumnModel(const QString& database, const QString& table, const QString& column) +{ + Column colObj(database, table, column); + if (columnMap.contains(colObj)) + return columnMap.value(colObj); + + return SqlQueryModelColumnPtr(); +} + +SqlQueryModelColumnPtr SqlQueryModel::getColumnModel(const QString& table, const QString& column) +{ + return getColumnModel("main", table, column); +} + +QList SqlQueryModel::getTableColumnModels(const QString& database, const QString& table) +{ + QList results; + foreach (SqlQueryModelColumnPtr modelColumn, columns) + { + if (modelColumn->database.compare(database, Qt::CaseInsensitive) != 0) + continue; + + if (modelColumn->table.compare(table, Qt::CaseInsensitive) != 0) + continue; + + results << modelColumn; + } + return results; +} + +QList SqlQueryModel::getTableColumnModels(const QString& table) +{ + return getTableColumnModels("main", table); +} + +void SqlQueryModel::loadData(SqlQueryPtr results) +{ + if (rowCount() > 0) + clear(); + + view->horizontalHeader()->show(); + + // Read columns first. It will be needed later. + readColumns(); + + // Load data + SqlResultsRowPtr row; + int rowIdx = 0; + int rowsPerPage = CFG_UI.General.NumberOfRowsPerPage.get(); + rowNumBase = getCurrentPage() * rowsPerPage + 1; + + updateColumnHeaderLabels(); + QList itemList; + while (results->hasNext() && rowIdx < rowsPerPage) + { + row = results->next(); + if (!row) + break; + + itemList = loadRow(row); + insertRow(rowIdx, itemList); + + if ((rowIdx % 100) == 0) + qApp->processEvents(); + + rowIdx++; + } +} + +QList SqlQueryModel::loadRow(SqlResultsRowPtr row) +{ + QList itemList; + SqlQueryItem* item = nullptr; + RowId rowId; + int colIdx = 0; + foreach (const QVariant& value, row->valueList().mid(rowIdColumns)) + { + item = new SqlQueryItem(); + rowId = getRowIdValue(row, colIdx); + updateItem(item, value, colIdx, rowId); + itemList << item; + colIdx++; + } + + return itemList; +} + +RowId SqlQueryModel::getRowIdValue(SqlResultsRowPtr row, int columnIdx) +{ + RowId rowId; + Table table = tablesForColumns[columnIdx]; + QHash rowIdColumns = tableToRowIdColumn[table]; + QHashIterator it(rowIdColumns); + QString col; + while (it.hasNext()) + { + // Check if the result row contains QueryExecutor's column alias for this RowId column + col = it.next().key(); + if (row->contains(col)) + { + // It does, do let's put the actual column name into the RowId and assign the RowId value to it. + // Using the actucal column name as a key will let create a proper query for updates, etc, later on. + rowId[it.value()] = row->value(col); + } + else if (columnEditionStatus[columnIdx]) + { + qCritical() << "No row ID column for cell that is editable. Asked for row ID column named:" << col + << "in table" << tablesForColumns[columnIdx].getTable(); + return RowId(); + } + } + return rowId; +} + +void SqlQueryModel::updateItem(SqlQueryItem* item, const QVariant& value, int columnIndex, const RowId& rowId) +{ + SqlQueryModelColumnPtr column = columns[columnIndex]; + Qt::Alignment alignment; + + if (column->isNumeric() && isNumeric(value)) + alignment = Qt::AlignRight|Qt::AlignVCenter; + else + alignment = Qt::AlignLeft|Qt::AlignVCenter; + + // This should be equal at most, unless we have UTF-8 string, than there might be more bytes. + // If less, than it's not limited. + bool limited = value.toByteArray().size() >= cellDataLengthLimit; + + item->setJustInsertedWithOutRowId(false); + item->setValue(value, limited, true); + item->setColumn(column.data()); + item->setTextAlignment(alignment); + item->setRowId(rowId); +} + +RowId SqlQueryModel::getNewRowId(const RowId& currentRowId, const QList items) +{ + if (currentRowId.size() > 1) + { + // For WITHOUT ROWID tables we need to look up all columns + QStringList rowIdColumns = currentRowId.keys(); + RowId newRowIdCandidate = currentRowId; + int idx; + for (SqlQueryItem* item : items) + { + if (rowIdColumns.contains(item->getColumn()->column, Qt::CaseInsensitive)) + { + idx = indexOf(rowIdColumns, item->getColumn()->column, Qt::CaseInsensitive); + newRowIdCandidate[rowIdColumns[idx]] = item->getValue(); + } + } + return newRowIdCandidate; + } + else + { + // Check for an update on the standard ROWID + SqlQueryModelColumn* col = nullptr; + for (SqlQueryItem* item : items) + { + col = item->getColumn(); + if (isRowIdKeyword(col->column) || col->isRowIdPk()) + { + RowId newRowId; + newRowId["ROWID"] = item->getValue(); + return newRowId; + } + } + } + + return currentRowId; +} + +void SqlQueryModel::updateRowIdForAllItems(const Table& table, const RowId& rowId, const RowId& newRowId) +{ + SqlQueryItem* item = nullptr; + for (int row = 0; row < rowCount(); row++) + { + for (int col = 0; col < columnCount(); col++) + { + item = itemFromIndex(row, col); + if (item->getColumn()->database.compare(table.getDatabase(), Qt::CaseInsensitive) != 0) + continue; + + if (item->getColumn()->table.compare(table.getTable(), Qt::CaseInsensitive) != 0) + continue; + + if (item->getRowId() != rowId) + continue; + + item->setRowId(newRowId); + } + } +} + +void SqlQueryModel::readColumns() +{ + columns.clear(); + tableToRowIdColumn.clear(); + + // Reading column mapping for ROWID columns + int totalRowIdCols = 0; + Table table; + foreach (const QueryExecutor::ResultRowIdColumnPtr& resCol, queryExecutor->getRowIdResultColumns()) + { + table.setDatabase(resCol->database); + table.setTable(resCol->table); + tableToRowIdColumn[table] = resCol->queryExecutorAliasToColumn; + totalRowIdCols += resCol->queryExecutorAliasToColumn.size(); + } + + // Reading column details (datatype, constraints) + readColumnDetails(); + + // Preparing other usful information about columns + rowIdColumns = totalRowIdCols; + tablesForColumns = getTablesForColumns(); + columnEditionStatus = getColumnEditionEnabledList(); +} + +void SqlQueryModel::readColumnDetails() +{ + // Preparing global (table oriented) edition forbidden reasons + QSet editionForbiddenGlobalReasons; + foreach (QueryExecutor::EditionForbiddenReason reason, queryExecutor->getEditionForbiddenGlobalReasons()) + editionForbiddenGlobalReasons << SqlQueryModelColumn::convert(reason); + + // Reading all the details from query executor source tables + QHash tableDetails = readTableDetails(); + + // Preparing for processing + Table table; + Column column; + TableDetails details; + TableDetails::ColumnDetails colDetails; + + SqlQueryModelColumnPtr modelColumn; + SqliteColumnTypePtr modelColumnType; + SqlQueryModelColumn::Constraint* modelConstraint = nullptr; + + foreach (const QueryExecutor::ResultColumnPtr& resCol, queryExecutor->getResultColumns()) + { + // Creating new column for the model (this includes column oriented forbidden reasons) + modelColumn = SqlQueryModelColumnPtr::create(resCol); + + // Adding global edition forbidden reasons + modelColumn->editionForbiddenReason += editionForbiddenGlobalReasons; + + // Getting details of given table and column + table = Table(modelColumn->database, modelColumn->table); + column = Column(modelColumn->database, modelColumn->table, modelColumn->column); + + details = tableDetails[table]; + colDetails = details.columns[modelColumn->column]; + + // Column type + modelColumnType = colDetails.type; + if (modelColumnType) + modelColumn->dataType = DataType(modelColumnType->name, modelColumnType->precision, modelColumnType->scale); + + // Column constraints + foreach (SqliteCreateTable::Column::ConstraintPtr constrPtr, colDetails.constraints) + { + modelConstraint = SqlQueryModelColumn::Constraint::create(constrPtr); + if (modelConstraint) + modelColumn->constraints << modelConstraint; + } + + // Table constraints + foreach (SqliteCreateTable::ConstraintPtr constrPtr, details.constraints) + { + modelConstraint = SqlQueryModelColumn::Constraint::create(modelColumn->column, constrPtr); + if (modelConstraint) + modelColumn->constraints << modelConstraint; + } + + // Adding to list for ordered access + columns << modelColumn; + + // Adding to hash for fast, key based access + columnMap[column] = modelColumn; + } +} + +QHash SqlQueryModel::readTableDetails() +{ + QHash results; + SqliteQueryPtr query; + SqliteCreateTablePtr createTable; + Dialect dialect = db->getDialect(); + SchemaResolver resolver(getDb()); + QString database; + Table table; + QString columnName; + + foreach (const QueryExecutor::SourceTablePtr& srcTable, queryExecutor->getSourceTables()) + { + database = srcTable->database.isEmpty() ? "main" : srcTable->database; + + query = resolver.getParsedObject(database, srcTable->table, SchemaResolver::TABLE); + if (!query || !query.dynamicCast()) + { + qWarning() << "Could not get parsed table while reading table details in SqlQueryModel. Queried table was:" + << database + "." + srcTable->table; + continue; + } + createTable = query.dynamicCast(); + + // Table details + TableDetails tableDetails; + table = {database, srcTable->table}; + + // Table constraints + foreach (SqliteCreateTable::Constraint* tableConstr, createTable->constraints) + tableDetails.constraints << tableConstr->detach(); + + // Table columns + foreach (SqliteCreateTable::Column* columnStmt, createTable->columns) + { + // Column details + TableDetails::ColumnDetails columnDetails; + columnName = stripObjName(columnStmt->name, dialect); + + // Column type + if (columnStmt->type) + columnDetails.type = columnStmt->type->detach(); + else + columnDetails.type = SqliteColumnTypePtr(); + + // Column constraints + foreach (SqliteCreateTable::Column::Constraint* columnConstr, columnStmt->constraints) + columnDetails.constraints << columnConstr->detach(); + + tableDetails.columns[columnName] = columnDetails; + } + + results[table] = tableDetails; + } + + return results; + +} + +QList SqlQueryModel::getTablesForColumns() +{ + QList
columnTables; + Table table; + foreach (SqlQueryModelColumnPtr column, columns) + { + if (column->editionForbiddenReason.size() > 0) + { + columnTables << Table(); + continue; + } + table = Table(column->database, column->table); + columnTables << table; + } + return columnTables; +} + +QList SqlQueryModel::getColumnEditionEnabledList() +{ + QList columnEditionEnabled; + foreach (SqlQueryModelColumnPtr column, columns) + columnEditionEnabled << (column->editionForbiddenReason.size() == 0); + + return columnEditionEnabled; +} + +void SqlQueryModel::updateColumnsHeader() +{ + QueryExecutor::SortList executorSortOrder = queryExecutor->getSortOrder(); + if (executorSortOrder.size() > 0) + emit sortingUpdated(executorSortOrder); +} + +void SqlQueryModel::updateColumnHeaderLabels() +{ + headerColumns.clear(); + foreach (SqlQueryModelColumnPtr column, columns) + { + headerColumns << column->displayName; + } + + setColumnCount(headerColumns.size()); +} + +void SqlQueryModel::handleExecFinished(SqlQueryPtr results) +{ + if (results->isError()) + { + emit executionFailed(tr("Error while executing SQL query: %1").arg(results->getErrorText())); + return; + } + + storeStep1NumbersFromExecution(); + loadData(results); + storeStep2NumbersFromExecution(); + + reloadAvailable = true; + + emit loadingEnded(true); + restoreNumbersToQueryExecutor(); + if (!reloading) + emit executionSuccessful(); + + reloading = false; + + if (queryExecutor->isRowCountingRequired() || rowCount() < CFG_UI.General.NumberOfRowsPerPage.get()) + emit totalRowsAndPagesAvailable(); // rows were counted manually + else + queryExecutor->countResults(); + +} + +void SqlQueryModel::handleExecFailed(int code, QString errorMessage) +{ + UNUSED(code); + + if (rowCount() > 0) + { + clear(); + columns.clear(); + updateColumnHeaderLabels(); + view->horizontalHeader()->hide(); + } + + emit loadingEnded(false); + if (reloading) + { + // If we were reloading, but it was interrupted, we don't want message about it. + if (!SqlErrorCode::isInterrupted(code)) + emit executionFailed(tr("Error while loading query results: %1").arg(errorMessage)); + } + else + emit executionFailed(tr("Error while executing SQL query: %1").arg(errorMessage)); + + restoreNumbersToQueryExecutor(); + resultsCountingFinished(0, 0, 0); + + reloading = false; +} + +void SqlQueryModel::resultsCountingFinished(quint64 rowsAffected, quint64 rowsReturned, int totalPages) +{ + this->rowsAffected = rowsAffected; + this->totalRowsReturned = rowsReturned; + this->totalPages = totalPages; + emit totalRowsAndPagesAvailable(); +} + +void SqlQueryModel::itemValueEdited(SqlQueryItem* item) +{ + UNUSED(item); + emit commitStatusChanged(getUncommitedItems().size() > 0); +} + +void SqlQueryModel::changeSorting(int logicalIndex, Qt::SortOrder order) +{ + if (!reloadAvailable) + return; + + queryExecutor->setSkipRowCounting(true); + queryExecutor->setSortOrder({QueryExecutor::Sort(order, logicalIndex)}); + reloadInternal(); +} + +void SqlQueryModel::changeSorting(int logicalIndex) +{ + Qt::SortOrder newOrder = Qt::AscendingOrder; + if (sortOrder.size() == 1) + { + switch (sortOrder.first().order) + { + case QueryExecutor::Sort::ASC: + newOrder = Qt::DescendingOrder; + break; + case QueryExecutor::Sort::DESC: + newOrder = Qt::AscendingOrder; + break; + case QueryExecutor::Sort::NONE: + newOrder = Qt::AscendingOrder; + break; + } + } + changeSorting(logicalIndex, newOrder); +} + +void SqlQueryModel::firstPage() +{ + if (!reloadAvailable) + return; + + queryExecutor->setSkipRowCounting(true); + queryExecutor->setPage(0); + reloadInternal(); +} + +void SqlQueryModel::prevPage() +{ + if (!reloadAvailable) + return; + + int newPage = page - 1; + if (newPage < 0) + newPage = 0; + + queryExecutor->setSkipRowCounting(true); + queryExecutor->setPage(newPage); + reloadInternal(); +} + +void SqlQueryModel::nextPage() +{ + if (!reloadAvailable) + return; + + int newPage = this->page + 1; + if ((newPage + 1) > totalPages) + newPage = totalPages - 1; + + queryExecutor->setSkipRowCounting(true); + queryExecutor->setPage(newPage); + reloadInternal(); +} + +void SqlQueryModel::lastPage() +{ + if (!reloadAvailable) + return; + + int page = totalPages - 1; + if (page < 0) // this should never happen, but let's have it just in case + { + qWarning() << "Page < 0 while calling SqlQueryModel::lastPage()"; + page = 0; + } + + queryExecutor->setSkipRowCounting(true); + queryExecutor->setPage(page); + reloadInternal(); +} + +void SqlQueryModel::gotoPage(int newPage) +{ + if (!reloadAvailable) + return; + + if (newPage < 0 || (newPage + 1) > totalPages) + newPage = 0; + + queryExecutor->setSkipRowCounting(true); + queryExecutor->setPage(newPage); + reloadInternal(); +} + +bool SqlQueryModel::canReload() +{ + return reloadAvailable; +} + +void SqlQueryModel::storeStep1NumbersFromExecution() +{ + lastExecutionTime = queryExecutor->getLastExecutionTime(); + page = queryExecutor->getPage(); + sortOrder = queryExecutor->getSortOrder(); + + if (!queryExecutor->getSkipRowCounting()) + { + rowsAffected = queryExecutor->getRowsAffected(); + totalPages = queryExecutor->getTotalPages(); + if (!queryExecutor->isRowCountingRequired()) + totalRowsReturned = queryExecutor->getTotalRowsReturned(); + } +} + +void SqlQueryModel::storeStep2NumbersFromExecution() +{ + if (!queryExecutor->getSkipRowCounting()) + { + if (queryExecutor->isRowCountingRequired() || rowCount() < CFG_UI.General.NumberOfRowsPerPage.get()) + totalRowsReturned = rowCount(); + } +} + +void SqlQueryModel::restoreNumbersToQueryExecutor() +{ + /* + * Currently only page and sort order have to be restored after failed execution, + * so reloading current data works on the old page and order, not the ones that were + * requested but never loaded successfully. + */ + queryExecutor->setPage(page); + queryExecutor->setSortOrder(sortOrder); + emit sortingUpdated(sortOrder); +} + +Db* SqlQueryModel::getDb() const +{ + return db; +} + +void SqlQueryModel::setDb(Db* value) +{ + db = value; + queryExecutor->setDb(db); +} + +QueryExecutor::SortList SqlQueryModel::getSortOrder() const +{ + return sortOrder; +} + +void SqlQueryModel::setSortOrder(const QueryExecutor::SortList& newSortOrder) +{ + sortOrder = newSortOrder; + + if (!reloadAvailable) + return; + + queryExecutor->setSkipRowCounting(true); + queryExecutor->setSortOrder(newSortOrder); + reloadInternal(); +} + +bool SqlQueryModel::wasSchemaModified() const +{ + return queryExecutor->wasSchemaModified(); +} + +void SqlQueryModel::updateSelectiveCommitRollbackActions(const QItemSelection& selected, const QItemSelection& deselected) +{ + UNUSED(selected); + UNUSED(deselected); + QList selectedItems = view->getSelectedItems(); + bool result = false; + if (selectedItems.size() > 0) + { + foreach (SqlQueryItem* item, selectedItems) + { + if (item->isUncommited()) + { + result = true; + break; + } + } + } + + emit selectiveCommitStatusChanged(result); +} + +void SqlQueryModel::addNewRowInternal(int rowIdx) +{ + QList items; + int colCnt = columnCount(); + SqlQueryItem* item = nullptr; + SqlQueryModelColumn* columnModel = nullptr; + for (int i = 0; i < colCnt; i++) + { + columnModel = columns[i].data(); + + item = new SqlQueryItem(); + item->setNewRow(true); + item->setUncommited(true); + item->setColumn(columnModel); + + items << item; + } + insertRow(rowIdx, items); + + if (rowIdx == 0) // when adding first row, we need to update header + updateColumnHeaderLabels(); + + view->selectionModel()->clear();; + view->setCurrentRow(rowIdx); + view->setFocus(); +} + +Icon& SqlQueryModel::getIconForIdx(int idx) const +{ + switch (idx) + { + case 0: + return ICONS.SORT_COUNT_01; + case 1: + return ICONS.SORT_COUNT_02; + case 2: + return ICONS.SORT_COUNT_03; + case 3: + return ICONS.SORT_COUNT_04; + case 4: + return ICONS.SORT_COUNT_05; + case 5: + return ICONS.SORT_COUNT_06; + case 6: + return ICONS.SORT_COUNT_07; + case 7: + return ICONS.SORT_COUNT_08; + case 8: + return ICONS.SORT_COUNT_09; + case 9: + return ICONS.SORT_COUNT_10; + case 10: + return ICONS.SORT_COUNT_11; + case 11: + return ICONS.SORT_COUNT_12; + case 12: + return ICONS.SORT_COUNT_13; + case 13: + return ICONS.SORT_COUNT_14; + case 14: + return ICONS.SORT_COUNT_15; + case 15: + return ICONS.SORT_COUNT_16; + case 16: + return ICONS.SORT_COUNT_17; + case 17: + return ICONS.SORT_COUNT_18; + case 18: + return ICONS.SORT_COUNT_19; + case 19: + return ICONS.SORT_COUNT_20; + } + return ICONS.SORT_COUNT_20_PLUS; +} + +void SqlQueryModel::addNewRow() +{ + int row = rowCount(); + SqlQueryItem* currentItem = view->getCurrentItem(); + if (currentItem) + row = currentItem->index().row(); + + addNewRowInternal(row); + + emit commitStatusChanged(true); +} + +void SqlQueryModel::addMultipleRows() +{ + bool ok; + int rows = QInputDialog::getInt(view, tr("Insert multiple rows"), tr("Number of rows to insert:"), 1, 1, 10000, 1, &ok); + if (!ok) + return; + + int row = rowCount(); + SqlQueryItem* currentItem = view->getCurrentItem(); + if (currentItem) + row = currentItem->index().row(); + + for (int i = 0; i < rows; i++) + addNewRowInternal(row); + + emit commitStatusChanged(true); +} + +void SqlQueryModel::deleteSelectedRows() +{ + QList selectedItems = view->getSelectedItems(); + QSet rows; + foreach (SqlQueryItem* item, selectedItems) + rows << item->index().row(); + + QList rowList = rows.toList(); + qSort(rowList); + + QList newItemsToDelete; + int cols = columnCount(); + foreach (int row, rowList) + { + for (int colIdx = 0; colIdx < cols; colIdx++) + { + SqlQueryItem* item = itemFromIndex(row, colIdx); + if (item->isNewRow()) + { + newItemsToDelete << item; + break; + } + + item->setDeletedRow(true); + item->setUncommited(true); + } + } + + foreach (SqlQueryItem* item, newItemsToDelete) + removeRow(item->index().row()); + + emit commitStatusChanged(getUncommitedItems().size() > 0); +} + +void SqlQueryModel::applySqlFilter(const QString& value) +{ + UNUSED(value); + // For custom query this is not supported. +} + +void SqlQueryModel::applyStringFilter(const QString& value) +{ + UNUSED(value); + // For custom query this is not supported. +} + +void SqlQueryModel::applyRegExpFilter(const QString& value) +{ + UNUSED(value); + // For custom query this is not supported. +} + +void SqlQueryModel::resetFilter() +{ + // For custom query this is not supported. +} + +int SqlQueryModel::columnCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return headerColumns.size(); +} + +QVariant SqlQueryModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole) + { + if (orientation == Qt::Horizontal) + { + if (section < 0 || section >= headerColumns.size()) + return QVariant(); + + return headerColumns[section]; + } + else + return rowNumBase + section; + } + + if (role == Qt::DecorationRole && orientation == Qt::Horizontal) + { + int idx = 0; + for (const QueryExecutor::Sort& sort : sortOrder) + { + if (sort.column == section) + { + bool desc = sort.order == QueryExecutor::Sort::DESC; + return *(getIconForIdx(idx).with(desc ? Icon::SORT_DESC : Icon::SORT_ASC)); + } + idx++; + } + return QVariant(); + } + + if (role == Qt::FontRole) + return CFG_UI.Fonts.DataView.get(); + + return QAbstractItemModel::headerData(section, orientation, role); +} + +bool SqlQueryModel::isExecutionInProgress() const +{ + return queryExecutor->isExecutionInProgress(); +} + +void SqlQueryModel::loadFullDataForEntireRow(int row) +{ + int colCnt = columns.size(); + SqlQueryItem *item = nullptr; + for (int col = 0; col < colCnt; col++) + { + item = itemFromIndex(row, col); + if (!item) + continue; + + if (!item->isLimitedValue()) + continue; + + item->loadFullData(); + } +} + +void SqlQueryModel::CommitUpdateQueryBuilder::clear() +{ + database.clear(); + table.clear(); + columns.clear(); + queryArgs.clear(); + conditions.clear(); + assignmentArgs.clear(); +} + +void SqlQueryModel::CommitUpdateQueryBuilder::setDatabase(const QString& database) +{ + this->database = database; +} + +void SqlQueryModel::CommitUpdateQueryBuilder::setTable(const QString& table) +{ + this->table = table; +} + +void SqlQueryModel::CommitUpdateQueryBuilder::setColumn(const QString& column) +{ + this->columns = {column}; +} + +void SqlQueryModel::CommitUpdateQueryBuilder::addColumn(const QString& column) +{ + columns << column; +} + +QString SqlQueryModel::CommitUpdateQueryBuilder::build() +{ + QString conditionsString = RowIdConditionBuilder::build(); + + QString dbAndTable; + if (!database.isNull()) + dbAndTable += database + "."; + + dbAndTable += table; + + int argIndex = 0; + QString arg; + QStringList assignments; + for (const QString& col : columns) + { + arg = ":value_" + QString::number(argIndex++); + assignmentArgs << arg; + assignments << col + " = " + arg; + } + + return "UPDATE " + dbAndTable + " SET "+ assignments.join(", ") +" WHERE " + conditionsString + ";"; +} + +QStringList SqlQueryModel::CommitUpdateQueryBuilder::getAssignmentArgs() const +{ + return assignmentArgs; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h new file mode 100644 index 0000000..cb626ae --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h @@ -0,0 +1,444 @@ +#ifndef SQLQUERYMODEL_H +#define SQLQUERYMODEL_H + +#include "db/db.h" +#include "db/sqlquery.h" +#include "db/queryexecutor.h" +#include "sqlquerymodelcolumn.h" +#include "parser/ast/sqlitecreatetable.h" +#include "common/column.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class SqlQueryItem; +class FormView; +class SqlQueryView; +class SqlQueryRowNumModel; + +class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel +{ + Q_OBJECT + + public: + enum Feature + { + INSERT_ROW = 0x01, + DELETE_ROW = 0x02, + FILTERING = 0x04 + }; + Q_DECLARE_FLAGS(Features, Feature) + + explicit SqlQueryModel(QObject *parent = 0); + virtual ~SqlQueryModel(); + + static void staticInit(); + static int getCellDataLengthLimit(); + + QString getQuery() const; + void setQuery(const QString &value); + void setExplainMode(bool explain); + Db* getDb() const; + void setDb(Db* value); + qint64 getExecutionTime(); + qint64 getTotalRowsReturned(); + qint64 getTotalRowsAffected(); + qint64 getTotalPages(); + QList getColumns(); + SqlQueryItem* itemFromIndex(const QModelIndex& index) const; + SqlQueryItem* itemFromIndex(int row, int column) const; + QModelIndexList findIndexes(int role, const QVariant &value, int hits = -1) const; + QModelIndexList findIndexes(const QModelIndex &start, const QModelIndex& end, int role, const QVariant &value, int hits = -1) const; + QList findItems(int role, const QVariant &value, int hits = -1) const; + QList findItems(const QModelIndex &start, const QModelIndex& end, int role, const QVariant &value, int hits = -1) const; + QList getUncommitedItems() const; + QList getRow(int row); + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + bool isExecutionInProgress() const; + void loadFullDataForEntireRow(int row); + + virtual Features features() const; + + /** + * @brief applySqlFilter + * @param value Filter expression. + * Default implementation does nothing. Working implementation (i.e. for a table) + * should set the query to temporary value which respects given filter and reload the data. + * Filter passed to this method is meant to be treated as SQL expression to be placed after WHERE clause. + */ + virtual void applySqlFilter(const QString& value); + + /** + * @brief applyStringFilter + * @param value Filter expression. + * Default implementation does nothing. Working implementation (i.e. for a table) + * should set the query to temporary value which respects given filter and reload the data. + * Filter passed to this method is meant to be treated as plain text to be matched in any column. + */ + virtual void applyStringFilter(const QString& value); + + /** + * @brief applyStringFilter + * @param value Filter expression. + * Default implementation does nothing. Working implementation (i.e. for a table) + * should set the query to temporary value which respects given filter and reload the data. + * Filter passed to this method is meant to be treated as regular expression to be matched in any column. + */ + virtual void applyRegExpFilter(const QString& value); + + /** + * @brief resetFilter + * Default implementation does nothing. Working implementation (i.e. for a table) + * should resets filter, so the data is no longer filtered. + */ + virtual void resetFilter(); + + /** + * @brief getCurrentPage Gets number of current results page + * @param includeOneBeingLoaded If true, then also the page that is currently being loaded (but not yet done) will returned over the currently presented page. + * @return Current page as 0-based index. If current page is not yet defined or paging is disabled, then this method returns 0. + * This method returns always the page that is currently presented in results, not the one that might be currently being queried. + * If you need to include the one being loaded (if any), then use getLoadingPage(). + */ + int getCurrentPage(bool includeOneBeingLoaded = false) const; + void gotoPage(int newPage); + bool canReload(); + + QueryExecutor::SortList getSortOrder() const; + void setSortOrder(const QueryExecutor::SortList& newSortOrder); + + /** + * @brief Tells if database schema was modified by last query executed. + * @return true if schema was modified, or false if not. + */ + bool wasSchemaModified() const; + + SqlQueryView* getView() const; + void setView(SqlQueryView* value); + + static QList> groupItemsByRows(const QList& items); + static QHash > groupItemsByTable(const QList& items); + + protected: + class CommitUpdateQueryBuilder : public RowIdConditionBuilder + { + public: + void clear(); + + void setDatabase(const QString& database); + void setTable(const QString& table); + void setColumn(const QString& column); + void addColumn(const QString& column); + + QString build(); + QStringList getAssignmentArgs() const; + + protected: + QString database; + QString table; + QStringList columns; + QStringList assignmentArgs; + }; + + /** + * @brief commitAddedRow Inserts new row to a table. + * @param itemsInRow All cells for the new row. + * @return true on success, false on failure. + * Default implementation does nothing and returns false, because inserting for custom query results is not possible. + * Inheriting class can reimplement this, so for example model specialized for single table can add rows. + * The method implementation should take items that are in model (and are passed to this method) + * and insert them into the actual database table. It also has to update items in the model, + * so they are no longer "new" and have the same data as inserted into the database. + */ + virtual bool commitAddedRow(const QList& itemsInRow); + + /** + * @brief commitEditedRow Updates table row with new values. + * @param itemsInRow Modified cell values. + * @return true on success, false on failure. + * Default implementation should be okay for most cases. It takes all modified cells and updates their + * values in table basing on the ROWID, database, table and column names - which are all available, + * unless the cell doesn't referr to the table, but in that case the cell should not be editable for user anyway. + * Important thing to pay attention to is that the item list passed in arguments contains only modified items. + */ + virtual bool commitEditedRow(const QList& itemsInRow); + + /** + * @brief commitDeletedRow Deletes row from the table. + * @param itemsInRow All cells for the deleted row. + * @return true on success, false on failure. + * Default implementation gets rid of row items from the model and that's all. + * Inheriting class can reimplement this, so for example model specialized for single table can delete rows. + * The method implementation should delete the row from the database. + */ + virtual bool commitDeletedRow(const QList& itemsInRow); + + /** + * @brief rollbackAddedRow + * @param itemsInRow All cells for the new row. + * Default implementation gets rid of row items from the model and that's all. + */ + virtual void rollbackAddedRow(const QList& itemsInRow); + + /** + * @brief rollbackEditedRow + * @param itemsInRow All cells for the deleted row. + * Restores original values in items. + */ + virtual void rollbackEditedRow(const QList& itemsInRow); + + /** + * @brief rollbackDeletedRow + * @param itemsInRow Modified cell values. + * The implementation should restore original values to items in the model. + * The default implementation is pretty much complete. It restores original state of row items. + */ + virtual void rollbackDeletedRow(const QList& itemsInRow); + + SqlQueryModelColumnPtr getColumnModel(const QString& database, const QString& table, const QString& column); + SqlQueryModelColumnPtr getColumnModel(const QString& table, const QString& column); + QList getTableColumnModels(const QString& database, const QString& table); + QList getTableColumnModels(const QString& table); + void updateItem(SqlQueryItem* item, const QVariant& value, int columnIndex, const RowId& rowId); + RowId getNewRowId(const RowId& currentRowId, const QList items); + void updateRowIdForAllItems(const Table& table, const RowId& rowId, const RowId& newRowId); + + QueryExecutor* queryExecutor = nullptr; + Db* db = nullptr; + QList columns; + + /** + * @brief Limit of data length in loaded cells. + * + * Bytes or utf-8 characters. + * Having this set to 10000 gives about 290 MB of memory consumption + * while having 30 columns and 1000 result rows loaded, all with 10000 bytes. + */ + static const int cellDataLengthLimit = 10000; + + private: + struct TableDetails + { + struct ColumnDetails + { + SqliteColumnTypePtr type; + QList constraints; + }; + + QHash columns; + QList constraints; + }; + + void loadData(SqlQueryPtr results); + QList loadRow(SqlResultsRowPtr row); + RowId getRowIdValue(SqlResultsRowPtr row, int columnIdx); + void readColumns(); + void readColumnDetails(); + void updateColumnsHeader(); + void updateColumnHeaderLabels(); + void executeQueryInternal(); + void internalExecutionStopped(); + QHash readTableDetails(); + QList
getTablesForColumns(); + QList getColumnEditionEnabledList(); + QList toItemList(const QModelIndexList& indexes) const; + bool commitRow(const QList& itemsInRow); + void rollbackRow(const QList& itemsInRow); + void storeStep1NumbersFromExecution(); + void storeStep2NumbersFromExecution(); + void restoreNumbersToQueryExecutor(); + QList filterOutCommitedItems(const QList& items); + void commitInternal(const QList& items); + void rollbackInternal(const QList& items); + void reloadInternal(); + void addNewRowInternal(int rowIdx); + Icon& getIconForIdx(int idx) const; + + QString query; + bool explain = false; + + /** + * @brief reloadAvailable + * This value is false by default and is changed only once - after first successful + * query execution. It's designed to report proper status by canReload(). + * Data reloading is available to user practically after any query was executed. + */ + bool reloadAvailable = false; + + /** + * @brief reloading + * This switch tells you if model is in the process of data reloading (true value) + * or initial query execution (then it's false). Data reloading takes place in any case + * when page is changed, order is changed, or simply user calls the data reloading. + * The initial query execution takes place when user calls "Execute query", + * which is translated to call to executeQuery(). + */ + bool reloading = false; + + /** + * @brief lastExecutionTime + * Keeps number of milliseconds that recently successfully executed query took to complete. + * If there was no such query executed, this will be 0. + */ + quint64 lastExecutionTime = 0; + + /** + * @brief totalRowsReturned + * Keeps number of rows returned from recently successfully executed query. + * If there was no such query executed, this will be 0. + */ + quint64 totalRowsReturned = 0; + + /** + * @brief rowsAffected + * Keeps number of rows affected by recently successfully executed query. + * If there was no such query executed, this will be 0. + */ + quint64 rowsAffected = 0; + + /** + * @brief totalPages + * Keeps number of pages available in recently successfully executed query. + * If there was no such query executed, this will be -1. + */ + int totalPages = -1; + + /** + * @brief page + * The page variable keeps page of recently sucessfly loaded data. + * If there was no successful data load, or when paging is disabled, then this will be -1. + */ + int page = -1; + + /** + * @brief sortOrder + * The sortOrder variable keeps sorting order of recently sucessfly loaded data. + * If column member of the sort object is -1, then no sorting is being aplied. + */ + QueryExecutor::SortList sortOrder; + + QHash columnMap; + QHash> tableToRowIdColumn; + QStringList headerColumns; + int rowNumBase = 0; + SqlQueryView* view = nullptr; + quint32 resultsCountingAsyncId = 0; + + /** + * @brief rowIdColumns + * We skip first this number of columns from the results of the SQL query, because those are ROWID columns. + * The query returns ROWID columns, because this is how QueryExecutor provides this information. + */ + int rowIdColumns = 0; + + /** + * @brief tablesForColumns + * List of tables associated to \link #columns by order index. + */ + QList
tablesForColumns; + + /** + * @brief columnEditionStatus + * List of column edition capabilities, in the same order as \link #columns. + */ + QList columnEditionStatus; + + private slots: + void handleExecFinished(SqlQueryPtr results); + void handleExecFailed(int code, QString errorMessage); + void resultsCountingFinished(quint64 rowsAffected, quint64 rowsReturned, int totalPages); + + public slots: + void itemValueEdited(SqlQueryItem* item); + void changeSorting(int logicalIndex, Qt::SortOrder order); + void changeSorting(int logicalIndex); + void firstPage(); + void prevPage(); + void nextPage(); + void lastPage(); + void executeQuery(); + void interrupt(); + void commit(); + void rollback(); + void commit(const QList& items); + void rollback(const QList& items); + void reload(); + void updateSelectiveCommitRollbackActions(const QItemSelection& selected, const QItemSelection& deselected); + void addNewRow(); + void addMultipleRows(); + void deleteSelectedRows(); + + signals: + /** + * @brief executionStarted + * + * Emitted just after query started executing. + */ + void executionStarted(); + + /** + * @brief executionSuccessful + * + * Emitted after initial query execution was successful. It's not emitted after data reloading of page changing. + */ + void executionSuccessful(); + + /** + * @brief executionFailed + * @param errorText + * + * Emitted after failed query execution, or data reloading failed or page changing failed. + */ + void executionFailed(const QString& errorText); + + /** + * @brief loadingEnded + * @param executionSuccessful + * + * Emitted every query execution, every data reloading and every page change. + */ + void loadingEnded(bool executionSuccessful); + + /** + * @brief totalRowsAndPagesAvailable + * + * Emitted when model finished querying total number of rows (and pages). + * This is asynchronously emitted after execution has finished, so counting doesn't block the model. + * It might not get emitted in some cases, like when there was an error when counting (it will be logged with qWarning()), + * or when counting was interrupted by executing query (the same, or modified). + * + * When the main query execution failed, this signal will be emitted to inform about total rows and pages being 0. + */ + void totalRowsAndPagesAvailable(); + + /** + * @brief commitStatusChanged + * @param commitAvailable Tells if there's anything to commit/rollback or not. + * + * Emitted after any results cell has been modified and can now be commited or rolled back. + * Also emitted after commit and rollback. + */ + void commitStatusChanged(bool commitAvailable); + + /** + * @brief selectiveCommitStatusChanged + * @param commitAvailable Tells if there's anything to commit/rollback or not. + * + * Emitted when user changes selection in the view, so if the selection includes any uncommited cells, + * then this signal will be emitted with parameter true, or if there is no uncommited cells, + * then it will be emitted with parameter false. + */ + void selectiveCommitStatusChanged(bool commitAvailable); + + /** + * @brief sortIndicatorUpdated + * + * Emitted after columns header sorting has been changed. + */ + void sortingUpdated(const QueryExecutor::SortList& sortOrder); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(SqlQueryModel::Features) + +#endif // SQLQUERYMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp new file mode 100644 index 0000000..62d0b45 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp @@ -0,0 +1,465 @@ +#include "sqlquerymodelcolumn.h" +#include "iconmanager.h" +#include + +SqlQueryModelColumn::SqlQueryModelColumn(const QueryExecutor::ResultColumnPtr& resultColumn) +{ + displayName = resultColumn->displayName; + column = resultColumn->column; + table = resultColumn->table; + database = resultColumn->database.isEmpty() ? "main": resultColumn->database; + foreach (QueryExecutor::ColumnEditionForbiddenReason reason, resultColumn->editionForbiddenReasons) + editionForbiddenReason << SqlQueryModelColumn::convert(reason); +} + +SqlQueryModelColumn::~SqlQueryModelColumn() +{ + foreach (Constraint* constr, constraints) + delete constr; + + constraints.clear(); +} + +void SqlQueryModelColumn::initMeta() +{ + qRegisterMetaType("SqlQueryModelColumn*"); + qRegisterMetaTypeStreamOperators("SqlQueryModelColumn*"); +} + +SqlQueryModelColumn::EditionForbiddenReason SqlQueryModelColumn::convert(QueryExecutor::EditionForbiddenReason reason) +{ + switch (reason) + { + case QueryExecutor::EditionForbiddenReason::NOT_A_SELECT: + return EditionForbiddenReason::NOT_A_SELECT; + case QueryExecutor::EditionForbiddenReason::SMART_EXECUTION_FAILED: + return EditionForbiddenReason::SMART_EXECUTION_FAILED; + } + return static_cast(-1); +} + +SqlQueryModelColumn::EditionForbiddenReason SqlQueryModelColumn::convert(QueryExecutor::ColumnEditionForbiddenReason reason) +{ + switch (reason) + { + case QueryExecutor::ColumnEditionForbiddenReason::EXPRESSION: + return EditionForbiddenReason::EXPRESSION; + case QueryExecutor::ColumnEditionForbiddenReason::SYSTEM_TABLE: + return EditionForbiddenReason::SYSTEM_TABLE; + case QueryExecutor::ColumnEditionForbiddenReason::COMPOUND_SELECT: + return EditionForbiddenReason::COMPOUND_SELECT; + case QueryExecutor::ColumnEditionForbiddenReason::GROUPED_RESULTS: + return EditionForbiddenReason::GROUPED_RESULTS; + case QueryExecutor::ColumnEditionForbiddenReason::DISTINCT_RESULTS: + return EditionForbiddenReason::DISTINCT_RESULTS; + case QueryExecutor::ColumnEditionForbiddenReason::COMM_TAB_EXPR: + return EditionForbiddenReason::COMMON_TABLE_EXPRESSION; + } + return static_cast(-1); +} + +QString SqlQueryModelColumn::resolveMessage(SqlQueryModelColumn::EditionForbiddenReason reason) +{ + switch (reason) + { + case EditionForbiddenReason::COMPOUND_SELECT: + return QObject::tr("Cannot edit columns that are result of compound SELECT statements (one that includes UNION, INTERSECT or EXCEPT keywords)."); + case EditionForbiddenReason::SMART_EXECUTION_FAILED: + return QObject::tr("The query execution mechanism had problems with extracting ROWID's properly. This might be a bug in the application. You may want to report this."); + case EditionForbiddenReason::EXPRESSION: + return QObject::tr("Requested column is a result of SQL expression, instead of a simple column selection. Such columns cannot be edited."); + case EditionForbiddenReason::SYSTEM_TABLE: + return QObject::tr("Requested column belongs to restricted SQLite table. Those tables cannot be edited directly."); + case EditionForbiddenReason::NOT_A_SELECT: + return QObject::tr("Cannot edit results of query other than SELECT."); + case EditionForbiddenReason::GROUPED_RESULTS: + return QObject::tr("Cannot edit columns that are result of aggregated SELECT statements."); + case EditionForbiddenReason::DISTINCT_RESULTS: + return QObject::tr("Cannot edit columns that are result of SELECT DISTINCT statement."); + case EditionForbiddenReason::COMMON_TABLE_EXPRESSION: + return QObject::tr("Cannot edit columns that are result of common table expression statement (%1).").arg("WITH ... SELECT ..."); + } + qCritical() << "Reached null text message for SqlQueryModel::EditionForbiddenReason. This should not happen!"; + return QString::null; +} + +bool SqlQueryModelColumn::isNumeric() +{ + return dataType.isNumeric(); +} + +bool SqlQueryModelColumn::canEdit() +{ + return editionForbiddenReason.size() == 0; +} + +QString SqlQueryModelColumn::getEditionForbiddenReason() +{ + if (canEdit()) + return QString::null; + + // We sort reasons to get most significant reason at first position. + QList list = editionForbiddenReason.toList(); + qSort(list); + return resolveMessage(list[0]); +} + +bool SqlQueryModelColumn::isPk() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isRowIdPk() const +{ + if (dataType.getType() != DataType::INTEGER) + return false; + + foreach (ConstraintPk* pk, getConstraints()) + if (pk->scope == Constraint::Scope::COLUMN) + return true; + + return false; +} + +bool SqlQueryModelColumn::isAutoIncr() const +{ + foreach (ConstraintPk* pk, getConstraints()) + if (pk->autoIncrement) + return true; + + return false; +} + +bool SqlQueryModelColumn::isNotNull() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isUnique() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isFk() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isDefault() const +{ + return getConstraints().size() > 0; +} + +bool SqlQueryModelColumn::isCollate() const +{ + return getConstraints().size() > 0; +} + +QList SqlQueryModelColumn::getFkConstraints() const +{ + return getConstraints(); +} + +SqlQueryModelColumn::ConstraintDefault* SqlQueryModelColumn::getDefaultConstraint() const +{ + QList list = getConstraints(); + if (list.size() == 0) + return nullptr; + + return list[0]; +} + +int qHash(SqlQueryModelColumn::EditionForbiddenReason reason) +{ + return static_cast(reason); +} + +QDataStream&operator <<(QDataStream& out, const SqlQueryModelColumn* col) +{ + out << reinterpret_cast(col); + return out; +} + +QDataStream&operator >>(QDataStream& in, SqlQueryModelColumn*& col) +{ + quint64 ptr; + in >> ptr; + col = reinterpret_cast(ptr); + return in; +} + + +SqlQueryModelColumn::Constraint* SqlQueryModelColumn::Constraint::create(const QString& column, SqliteCreateTable::ConstraintPtr tableConstraint) +{ + Constraint* constr = nullptr; + switch (tableConstraint->type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + { + if (!tableConstraint->doesAffectColumn(column)) + return nullptr; + + constr = new ConstraintPk(); + constr->type = Type::PRIMARY_KEY; + break; + } + case SqliteCreateTable::Constraint::UNIQUE: + { + constr = new ConstraintUnique(); + constr->type = Type::UNIQUE; + break; + } + case SqliteCreateTable::Constraint::CHECK: + { + ConstraintCheck* check = new ConstraintCheck(); + check->condition = tableConstraint->expr->detokenize(); + constr = check; + constr->type = Type::CHECK; + break; + } + case SqliteCreateTable::Constraint::FOREIGN_KEY: + { + int idx = tableConstraint->getAffectedColumnIdx(column); + if (idx < 0 || tableConstraint->foreignKey->indexedColumns.size() <= idx) + { + qWarning() << "Could not find FK column for definition:" << tableConstraint->detokenize(); + return nullptr; + } + + ConstraintFk* fk = new ConstraintFk(); + fk->foreignTable = tableConstraint->foreignKey->foreignTable; + fk->foreignColumn = tableConstraint->foreignKey->indexedColumns[idx]->name; + + constr = fk; + constr->type = Type::FOREIGN_KEY; + break; + } + default: + return nullptr; + } + + constr->scope = Scope::TABLE; + constr->definition = tableConstraint->detokenize(); + return constr; +} + +SqlQueryModelColumn::Constraint* SqlQueryModelColumn::Constraint::create(SqliteCreateTable::Column::ConstraintPtr columnConstraint) +{ + Constraint* constr = nullptr; + switch (columnConstraint->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + { + ConstraintPk* pk = new ConstraintPk(); + pk->autoIncrement = columnConstraint->autoincrKw; + constr = pk; + constr->type = Type::PRIMARY_KEY; + break; + } + case SqliteCreateTable::Column::Constraint::NOT_NULL: + { + constr = new ConstraintNotNull(); + constr->type = Type::NOT_NULL; + break; + } + case SqliteCreateTable::Column::Constraint::UNIQUE: + { + constr = new ConstraintUnique(); + constr->type = Type::UNIQUE; + break; + } + case SqliteCreateTable::Column::Constraint::CHECK: + { + ConstraintCheck* check = new ConstraintCheck(); + check->condition = columnConstraint->expr->detokenize(); + constr = check; + constr->type = Type::CHECK; + break; + } + case SqliteCreateTable::Column::Constraint::DEFAULT: + { + ConstraintDefault* def = new ConstraintDefault(); + if (!columnConstraint->id.isNull()) + def->defaultValue = columnConstraint->id; + else if (!columnConstraint->ctime.isNull()) + def->defaultValue = columnConstraint->ctime; + else if (columnConstraint->expr) + def->defaultValue = columnConstraint->expr->detokenize(); + else + def->defaultValue = columnConstraint->literalValue.toString(); + + constr = def; + constr->type = Type::DEFAULT; + break; + } + case SqliteCreateTable::Column::Constraint::COLLATE: + { + ConstraintCollate* collate = new ConstraintCollate(); + collate->collationName = columnConstraint->collationName; + constr = collate; + constr->type = Type::COLLATE; + break; + } + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + { + if (columnConstraint->foreignKey->indexedColumns.size() == 0) + { + qWarning() << "No foreign column defined for FK column constraint while creating SqlQueryModelColumn::Constraint."; + return nullptr; + } + + ConstraintFk* fk = new ConstraintFk(); + fk->foreignTable = columnConstraint->foreignKey->foreignTable; + fk->foreignColumn = columnConstraint->foreignKey->indexedColumns.first()->name; + + constr = fk; + constr->type = Type::FOREIGN_KEY; + break; + } + default: + return nullptr; + } + + constr->scope = Scope::COLUMN; + constr->definition = columnConstraint->detokenize(); + return constr; +} + +template +QList SqlQueryModelColumn::getConstraints() const +{ + QList results; + foreach (Constraint* constr, constraints) + if (dynamic_cast(constr)) + results << dynamic_cast(constr); + + return results; +} + + +QString SqlQueryModelColumn::ConstraintPk::getTypeString() const +{ + return "PRIMARY KEY"; +} + +QString SqlQueryModelColumn::ConstraintPk::getDetails() const +{ + QStringList detailList; + if (autoIncrement) + detailList << "AUTOINCREMENT"; + + if (onConflict != SqliteConflictAlgo::null) + detailList << QObject::tr("on conflict: %1", "data view tooltip").arg(sqliteConflictAlgo(onConflict)); + + if (detailList.size() > 0) + return "("+detailList.join(", ")+")"; + + return ""; +} + +Icon* SqlQueryModelColumn::ConstraintPk::getIcon() const +{ + return ICONS.CONSTRAINT_PRIMARY_KEY; +} + +QString SqlQueryModelColumn::ConstraintFk::getTypeString() const +{ + return "FOREIGN KEY"; +} + +QString SqlQueryModelColumn::ConstraintFk::getDetails() const +{ + return "("+QObject::tr("references table %1, column %2", "data view tooltip").arg(foreignTable).arg(foreignColumn)+")"; +} + +Icon* SqlQueryModelColumn::ConstraintFk::getIcon() const +{ + return ICONS.CONSTRAINT_FOREIGN_KEY; +} + +QString SqlQueryModelColumn::ConstraintUnique::getTypeString() const +{ + return "UNIQUE"; +} + +QString SqlQueryModelColumn::ConstraintUnique::getDetails() const +{ + if (onConflict != SqliteConflictAlgo::null) + return "("+QObject::tr("on conflict: %1", "data view tooltip").arg(sqliteConflictAlgo(onConflict))+")"; + + return QString::null; +} + +Icon* SqlQueryModelColumn::ConstraintUnique::getIcon() const +{ + return ICONS.CONSTRAINT_UNIQUE; +} + +QString SqlQueryModelColumn::ConstraintNotNull::getTypeString() const +{ + return "NOT NULL"; +} + +QString SqlQueryModelColumn::ConstraintNotNull::getDetails() const +{ + if (onConflict != SqliteConflictAlgo::null) + return "("+QObject::tr("on conflict: %1", "data view tooltip").arg(sqliteConflictAlgo(onConflict))+")"; + + return QString::null; +} + +Icon* SqlQueryModelColumn::ConstraintNotNull::getIcon() const +{ + return ICONS.CONSTRAINT_NOT_NULL; +} + +QString SqlQueryModelColumn::ConstraintDefault::getTypeString() const +{ + return "DEFAULT"; +} + +QString SqlQueryModelColumn::ConstraintDefault::getDetails() const +{ + return "("+defaultValue+")"; +} + +Icon* SqlQueryModelColumn::ConstraintDefault::getIcon() const +{ + return ICONS.CONSTRAINT_DEFAULT; +} + +QString SqlQueryModelColumn::ConstraintCheck::getTypeString() const +{ + return "CHECK"; +} + +QString SqlQueryModelColumn::ConstraintCheck::getDetails() const +{ + QStringList detailList; + detailList << QObject::tr("condition: %1", "data view tooltip").arg(condition); + + if (onConflict != SqliteConflictAlgo::null) + detailList << QObject::tr("on conflict: %1", "data view tooltip").arg(sqliteConflictAlgo(onConflict)); + + return "("+detailList.join(", ")+")"; +} + +Icon* SqlQueryModelColumn::ConstraintCheck::getIcon() const +{ + return ICONS.CONSTRAINT_CHECK; +} + +QString SqlQueryModelColumn::ConstraintCollate::getTypeString() const +{ + return "COLLATE"; +} + +QString SqlQueryModelColumn::ConstraintCollate::getDetails() const +{ + return "("+QObject::tr("collation name: %1", "data view tooltip").arg(collationName)+")"; +} + +Icon* SqlQueryModelColumn::ConstraintCollate::getIcon() const +{ + return ICONS.CONSTRAINT_COLLATION; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h new file mode 100644 index 0000000..fb55fe5 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h @@ -0,0 +1,177 @@ +#ifndef SQLQUERYMODELCOLUMN_H +#define SQLQUERYMODELCOLUMN_H + +#include "db/queryexecutor.h" +#include "parser/ast/sqlitecreatetable.h" +#include "datatype.h" +#include "common/global.h" +#include "guiSQLiteStudio_global.h" + +class Icon; + +class GUI_API_EXPORT SqlQueryModelColumn +{ + public: + /** + * @brief The EditionForbiddenReason enum + * Order of this enum is important, because when user requests item edition, + * then reason for edition forbidden (if any) is taken as specified order. + * The earlier item is in the enum, the more significant it is and user + * will be notified with the more significant reason before any other. + */ + enum class EditionForbiddenReason + { + SYSTEM_TABLE, + NOT_A_SELECT, + COMPOUND_SELECT, + GROUPED_RESULTS, + EXPRESSION, + SMART_EXECUTION_FAILED, + DISTINCT_RESULTS, + COMMON_TABLE_EXPRESSION + }; + + struct Constraint + { + enum class Type + { + PRIMARY_KEY, + NOT_NULL, + UNIQUE, + CHECK, + DEFAULT, + COLLATE, + FOREIGN_KEY, + null + }; + + enum class Scope + { + TABLE, + COLUMN + }; + + virtual ~Constraint() {} + + static Constraint* create(const QString& column, SqliteCreateTable::ConstraintPtr tableConstraint); + static Constraint* create(SqliteCreateTable::Column::ConstraintPtr columnConstraint); + + virtual QString getTypeString() const = 0; + virtual QString getDetails() const = 0; + virtual Icon* getIcon() const = 0; + + Type type; + Scope scope; + QString definition; + }; + + struct ConstraintPk : public Constraint + { + QString getTypeString() const; + QString getDetails() const; + Icon* getIcon() const; + + bool autoIncrement; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + }; + + struct ConstraintFk : public Constraint + { + QString getTypeString() const; + QString getDetails() const; + Icon* getIcon() const; + + QString foreignTable; + QString foreignColumn; + }; + + struct ConstraintUnique : public Constraint + { + QString getTypeString() const; + QString getDetails() const; + Icon* getIcon() const; + + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + }; + + struct ConstraintNotNull : public Constraint + { + QString getTypeString() const; + QString getDetails() const; + Icon* getIcon() const; + + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + }; + + struct ConstraintDefault : public Constraint + { + QString getTypeString() const; + QString getDetails() const; + Icon* getIcon() const; + + QString defaultValue; + }; + + struct ConstraintCheck : public Constraint + { + QString getTypeString() const; + QString getDetails() const; + Icon* getIcon() const; + + QString condition; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + }; + + struct ConstraintCollate : public Constraint + { + QString getTypeString() const; + QString getDetails() const; + Icon* getIcon() const; + + QString collationName; + }; + + SqlQueryModelColumn(const QueryExecutor::ResultColumnPtr& resultColumn); + virtual ~SqlQueryModelColumn(); + + static void initMeta(); + static EditionForbiddenReason convert(QueryExecutor::EditionForbiddenReason reason); + static EditionForbiddenReason convert(QueryExecutor::ColumnEditionForbiddenReason reason); + static QString resolveMessage(EditionForbiddenReason reason); + bool isNumeric(); + bool canEdit(); + QString getEditionForbiddenReason(); + bool isPk() const; + bool isRowIdPk() const; + bool isAutoIncr() const; + bool isNotNull() const; + bool isUnique() const; + bool isFk() const; + bool isDefault() const; + bool isCollate() const; + QList getFkConstraints() const; + ConstraintDefault* getDefaultConstraint() const; + + QString displayName; + QString column; + QString table; + QString database; + DataType dataType; + QSet editionForbiddenReason; + QList constraints; + + private: + template + QList getConstraints() const; +}; + +typedef QSharedPointer SqlQueryModelColumnPtr; + +int qHash(SqlQueryModelColumn::EditionForbiddenReason reason); + +QDataStream &operator<<(QDataStream &out, const SqlQueryModelColumn* col); +QDataStream &operator>>(QDataStream &in, SqlQueryModelColumn*& col); + +Q_DECLARE_METATYPE(SqlQueryModelColumn*) + +#endif // SQLQUERYMODELCOLUMN_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp new file mode 100644 index 0000000..f494bd9 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.cpp @@ -0,0 +1,63 @@ +#include "sqlqueryrownummodel.h" +#include "common/unused.h" + +SqlQueryRowNumModel::SqlQueryRowNumModel(QAbstractItemModel *value, QObject *parent) : + QAbstractItemModel(parent) +{ + mainModel = value; +} + +QModelIndex SqlQueryRowNumModel::index(int row, int column, const QModelIndex &parent) const +{ + UNUSED(row); + UNUSED(column); + UNUSED(parent); + return QModelIndex(); +} + +QModelIndex SqlQueryRowNumModel::parent(const QModelIndex &child) const +{ + UNUSED(child); + return QModelIndex(); +} + +int SqlQueryRowNumModel::rowCount(const QModelIndex &parent) const +{ + UNUSED(parent); + + if (!mainModel) + return 0; + + return mainModel->rowCount(); +} + +int SqlQueryRowNumModel::columnCount(const QModelIndex &parent) const +{ + UNUSED(parent); + + if (!mainModel) + return 0; + + return mainModel->columnCount(); +} + +QVariant SqlQueryRowNumModel::data(const QModelIndex &index, int role) const +{ + UNUSED(index); + UNUSED(role); + return QVariant(); +} + +QVariant SqlQueryRowNumModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + UNUSED(role); + if (orientation == Qt::Horizontal) + return QVariant(); + + return rowNumBase + section; +} + +void SqlQueryRowNumModel::setRowNumBase(int value) +{ + rowNumBase = value; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h new file mode 100644 index 0000000..962e1d3 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryrownummodel.h @@ -0,0 +1,27 @@ +#ifndef SQLQUERYROWNUMMODEL_H +#define SQLQUERYROWNUMMODEL_H + +#include "guiSQLiteStudio_global.h" +#include + +class GUI_API_EXPORT SqlQueryRowNumModel : public QAbstractItemModel +{ + Q_OBJECT + public: + SqlQueryRowNumModel(QAbstractItemModel *value, QObject *parent = 0); + + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + void setRowNumBase(int value); + + private: + int rowNumBase = 1; + QAbstractItemModel* mainModel = nullptr; +}; + +#endif // SQLQUERYROWNUMMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp new file mode 100644 index 0000000..e4a0656 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp @@ -0,0 +1,454 @@ +#include "sqlqueryview.h" +#include "sqlqueryitemdelegate.h" +#include "sqlquerymodel.h" +#include "sqlqueryitem.h" +#include "common/widgetcover.h" +#include "tsvserializer.h" +#include "iconmanager.h" +#include "common/unused.h" +#include "common/extaction.h" +#include "multieditor/multieditor.h" +#include "multieditor/multieditordialog.h" +#include "uiconfig.h" +#include "dialogs/sortdialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(SqlQueryView) + +SqlQueryView::SqlQueryView(QWidget *parent) : + QTableView(parent) +{ + init(); +} + +SqlQueryView::~SqlQueryView() +{ + delete itemDelegate; +} + +QList SqlQueryView::getSelectedItems() +{ + QList items; + QModelIndexList idxList = selectionModel()->selectedIndexes(); + QModelIndex currIdx = getCurrentIndex(); + if (!idxList.contains(currIdx) && currIdx.isValid()) + idxList << currIdx; + + if (idxList.size() == 0) + return items; + + qSort(idxList); + const SqlQueryModel* model = dynamic_cast(idxList.first().model()); + foreach (const QModelIndex& idx, idxList) + items << model->itemFromIndex(idx); + + return items; +} + +SqlQueryItem* SqlQueryView::getCurrentItem() +{ + QModelIndex idx = getCurrentIndex(); + if (!idx.isValid()) + return nullptr; + + return getModel()->itemFromIndex(idx); +} + +SqlQueryModel* SqlQueryView::getModel() +{ + return dynamic_cast(model()); +} + +void SqlQueryView::setModel(QAbstractItemModel* model) +{ + QTableView::setModel(model); + connect(widgetCover, SIGNAL(cancelClicked()), getModel(), SLOT(interrupt())); + connect(getModel(), &SqlQueryModel::commitStatusChanged, this, &SqlQueryView::updateCommitRollbackActions); + connect(getModel(), &SqlQueryModel::sortingUpdated, this, &SqlQueryView::sortingUpdated); +} + +SqlQueryItem* SqlQueryView::itemAt(const QPoint& pos) +{ + return dynamic_cast(getModel()->itemFromIndex(indexAt(pos))); +} + +QToolBar* SqlQueryView::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return nullptr; +} + +void SqlQueryView::addAdditionalAction(QAction* action) +{ + additionalActions << action; +} + +QModelIndex SqlQueryView::getCurrentIndex() const +{ + return currentIndex(); +} + +void SqlQueryView::mouseDoubleClickEvent(QMouseEvent* event) +{ + SqlQueryItem* item = itemAt(event->pos()); + if (item && !handleDoubleClick(item)) + return; + + QTableView::mouseDoubleClickEvent(event); +} + +void SqlQueryView::init() +{ + itemDelegate = new SqlQueryItemDelegate(); + setItemDelegate(itemDelegate); + setMouseTracking(true); + + setContextMenuPolicy(Qt::CustomContextMenu); + contextMenu = new QMenu(this); + + connect(this, &QWidget::customContextMenuRequested, this, &SqlQueryView::customContextMenuRequested); + connect(CFG_UI.Fonts.DataView, SIGNAL(changed(QVariant)), this, SLOT(updateFont())); + + horizontalHeader()->setSortIndicatorShown(false); + horizontalHeader()->setSectionsClickable(true); + updateFont(); + + setupWidgetCover(); + initActions(); + setupHeaderMenu(); +} + +void SqlQueryView::setupWidgetCover() +{ + widgetCover = new WidgetCover(this); + widgetCover->initWithInterruptContainer(); +} + +void SqlQueryView::createActions() +{ + createAction(COPY, ICONS.ACT_COPY, tr("Copy"), this, SLOT(copy()), this); + createAction(COPY_AS, ICONS.ACT_COPY, tr("Copy as..."), this, SLOT(copyAs()), this); + createAction(PASTE, ICONS.ACT_PASTE, tr("Paste"), this, SLOT(paste()), this); + createAction(PASTE_AS, ICONS.ACT_PASTE, tr("Paste as..."), this, SLOT(pasteAs()), this); + createAction(SET_NULL, ICONS.SET_NULL, tr("Set NULL values"), this, SLOT(setNull()), this); + createAction(ERASE, ICONS.ERASE, tr("Erase values"), this, SLOT(erase()), this); + createAction(OPEN_VALUE_EDITOR, ICONS.OPEN_VALUE_EDITOR, tr("Edit value in editor"), this, SLOT(openValueEditor()), this); + createAction(COMMIT, ICONS.COMMIT, tr("Commit"), this, SLOT(commit()), this); + createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback"), this, SLOT(rollback()), this); + createAction(SELECTIVE_COMMIT, ICONS.COMMIT, tr("Commit selected cells"), this, SLOT(selectiveCommit()), this); + createAction(SELECTIVE_ROLLBACK, ICONS.ROLLBACK, tr("Rollback selected cells"), this, SLOT(selectiveRollback()), this); + createAction(SORT_DIALOG, ICONS.SORT_COLUMNS, tr("Define columns to sort by"), this, SLOT(openSortDialog()), this); + createAction(RESET_SORTING, ICONS.SORT_RESET, tr("Remove custom sorting"), this, SLOT(resetSorting()), this); + createAction(INSERT_ROW, ICONS.INSERT_ROW, tr("Insert row"), this, SIGNAL(requestForRowInsert()), this); + createAction(INSERT_MULTIPLE_ROWS, ICONS.INSERT_ROWS, tr("Insert multiple rows"), this, SIGNAL(requestForMultipleRowInsert()), this); + createAction(DELETE_ROW, ICONS.DELETE_ROW, tr("Delete selected row"), this, SIGNAL(requestForRowDelete()), this); + + actionMap[RESET_SORTING]->setEnabled(false); +} + +void SqlQueryView::setupDefShortcuts() +{ + setShortcutContext({ROLLBACK, SET_NULL, ERASE, OPEN_VALUE_EDITOR, COMMIT, COPY, COPY_AS, + PASTE, PASTE_AS}, Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(SqlQueryView, Action); +} + +void SqlQueryView::setupActionsForMenu(SqlQueryItem* currentItem, const QList& selectedItems) +{ + UNUSED(currentItem); + + // Selected items count + int selCount = selectedItems.size(); + + // Uncommited items count + QList uncommitedItems = getModel()->getUncommitedItems(); + int uncommitedCount = uncommitedItems.size(); + + // Uncommited & selected items count + int uncommitedSelCount = 0; + foreach (SqlQueryItem* item, uncommitedItems) + if (selectedItems.contains(item)) + uncommitedSelCount++; + + if (uncommitedCount > 0) + contextMenu->addAction(actionMap[COMMIT]); + + if (uncommitedSelCount > 0) + contextMenu->addAction(actionMap[SELECTIVE_COMMIT]); + + if (uncommitedCount > 0) + contextMenu->addAction(actionMap[ROLLBACK]); + + if (uncommitedSelCount > 0) + contextMenu->addAction(actionMap[SELECTIVE_ROLLBACK]); + + if (uncommitedCount > 0 && selCount > 0) + contextMenu->addSeparator(); + + if (selCount > 0) + { + contextMenu->addAction(actionMap[ERASE]); + contextMenu->addAction(actionMap[SET_NULL]); + contextMenu->addAction(actionMap[OPEN_VALUE_EDITOR]); + contextMenu->addSeparator(); + } + + if (selCount > 0) + { + contextMenu->addAction(actionMap[COPY]); + //contextMenu->addAction(actionMap[COPY_AS]); // TODO uncomment when implemented + contextMenu->addAction(actionMap[PASTE]); + //contextMenu->addAction(actionMap[PASTE_AS]); // TODO uncomment when implemented + } + if (additionalActions.size() > 0) + { + contextMenu->addSeparator(); + foreach (QAction* action, additionalActions) + contextMenu->addAction(action); + } +} + +void SqlQueryView::setupHeaderMenu() +{ + horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(horizontalHeader(), &QWidget::customContextMenuRequested, this, &SqlQueryView::headerContextMenuRequested); + headerContextMenu = new QMenu(horizontalHeader()); + headerContextMenu->addAction(actionMap[SORT_DIALOG]); + headerContextMenu->addAction(actionMap[RESET_SORTING]); +} + +bool SqlQueryView::handleDoubleClick(SqlQueryItem* item) +{ + if (item->getColumn()->dataType.getType() == DataType::BLOB) + { + openValueEditor(item); + return false; + } + return true; +} + +void SqlQueryView::updateCommitRollbackActions(bool enabled) +{ + actionMap[COMMIT]->setEnabled(enabled); + actionMap[ROLLBACK]->setEnabled(enabled); +} + +void SqlQueryView::customContextMenuRequested(const QPoint& pos) +{ + SqlQueryItem* currentItem = getCurrentItem(); + QList selectedItems = getSelectedItems(); + + contextMenu->clear(); + + setupActionsForMenu(currentItem, selectedItems); + emit contextMenuRequested(currentItem, selectedItems); + + if (contextMenu->actions().size() == 0) + return; + + contextMenu->popup(viewport()->mapToGlobal(pos)); +} + +void SqlQueryView::headerContextMenuRequested(const QPoint& pos) +{ + headerContextMenu->popup(horizontalHeader()->mapToGlobal(pos)); +} + +void SqlQueryView::openSortDialog() +{ + QStringList columns; + for (SqlQueryModelColumnPtr col : getModel()->getColumns()) + columns << col->displayName; + + SortDialog dialog(this); + dialog.setColumns(columns); + dialog.setSortOrder(getModel()->getSortOrder()); + if (dialog.exec() != QDialog::Accepted) + return; + + getModel()->setSortOrder(dialog.getSortOrder()); +} + +void SqlQueryView::resetSorting() +{ + getModel()->setSortOrder(QueryExecutor::SortList()); +} + +void SqlQueryView::sortingUpdated(const QueryExecutor::SortList& sortOrder) +{ + actionMap[RESET_SORTING]->setEnabled(sortOrder.size() > 0); +} + +void SqlQueryView::updateFont() +{ + QFont f = CFG_UI.Fonts.DataView.get(); + QFontMetrics fm(f); + verticalHeader()->setDefaultSectionSize(fm.height() + 4); +} + +void SqlQueryView::executionStarted() +{ + widgetCover->show(); +} + +void SqlQueryView::executionEnded() +{ + widgetCover->hide(); +} + +void SqlQueryView::setCurrentRow(int row) +{ + setCurrentIndex(model()->index(row, 0)); +} + +void SqlQueryView::copy() +{ + QList selectedItems = getSelectedItems(); + QList > groupedItems = SqlQueryModel::groupItemsByRows(selectedItems); + + QStringList cells; + QList rows; + + foreach (const QList& itemsInRows, groupedItems) + { + foreach (SqlQueryItem* item, itemsInRows) + cells << item->getFullValue().toString(); + + rows << cells; + cells.clear(); + } + + QString tsv = TsvSerializer::serialize(rows); + qApp->clipboard()->setText(tsv); +} + +void SqlQueryView::paste() +{ + QList deserializedRows = TsvSerializer::deserialize(qApp->clipboard()->text()); + + QList selectedItems = getSelectedItems(); + qSort(selectedItems); + SqlQueryItem* topLeft = selectedItems.first(); + + int columnCount = getModel()->columnCount(); + int rowCount = getModel()->rowCount(); + int rowIdx = topLeft->row(); + int colIdx = topLeft->column(); + + SqlQueryItem* item = nullptr; + + foreach (const QStringList& cells, deserializedRows) + { + // Check if we're out of rows range + if (rowIdx >= rowCount) + { + // No more rows available. + qDebug() << "Tried to paste more rows than available in the grid."; + break; + } + + foreach (const QString& cell, cells) + { + // Get current cell + if (colIdx >= columnCount) + { + // No more columns available. + qDebug() << "Tried to paste more columns than available in the grid."; + break; + } + item = getModel()->itemFromIndex(rowIdx, colIdx); + + // Set value to the cell + item->setValue(cell, false, false); + + // Go to next cell + colIdx++; + } + + // Go to next row, first cell + rowIdx++; + colIdx = topLeft->column(); + } +} + +void SqlQueryView::copyAs() +{ + // TODO copyAs() +} + +void SqlQueryView::pasteAs() +{ + // TODO pasteAs() +} + +void SqlQueryView::setNull() +{ + foreach (SqlQueryItem* selItem, getSelectedItems()) + selItem->setValue(QVariant(QString::null), false, false); +} + +void SqlQueryView::erase() +{ + foreach (SqlQueryItem* selItem, getSelectedItems()) + selItem->setValue("", false, false); +} + +void SqlQueryView::commit() +{ + getModel()->commit(); +} + +void SqlQueryView::rollback() +{ + getModel()->rollback(); +} + +void SqlQueryView::selectiveCommit() +{ + getModel()->commit(getSelectedItems()); +} + +void SqlQueryView::selectiveRollback() +{ + getModel()->rollback(getSelectedItems()); +} + +void SqlQueryView::openValueEditor(SqlQueryItem* item) +{ + if (!item) + { + qWarning() << "Tried to open value editor while there's no current item. It should not be called in that case."; + return; + } + + MultiEditorDialog editor(this); + editor.setWindowTitle(tr("Edit value")); + editor.setDataType(item->getColumn()->dataType); + editor.setValue(item->getFullValue()); + editor.setReadOnly(!item->getColumn()->canEdit()); + if (editor.exec() == QDialog::Rejected) + return; + + item->setValue(editor.getValue()); +} + +void SqlQueryView::openValueEditor() +{ + SqlQueryItem* currentItem = getCurrentItem(); + openValueEditor(currentItem); +} + +int qHash(SqlQueryView::Action action) +{ + return static_cast(action); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h new file mode 100644 index 0000000..65486fe --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h @@ -0,0 +1,130 @@ +#ifndef SQLQUERYVIEW_H +#define SQLQUERYVIEW_H + +#include "csvformat.h" +#include "common/extactioncontainer.h" +#include "db/queryexecutor.h" +#include "guiSQLiteStudio_global.h" +#include + +class SqlQueryItemDelegate; +class SqlQueryItem; +class WidgetCover; +class SqlQueryModel; +class SqlQueryModelColumn; +class QPushButton; +class QProgressBar; +class QMenu; + +CFG_KEY_LIST(SqlQueryView, QObject::tr("Data grid view"), + CFG_KEY_ENTRY(COPY, Qt::CTRL + Qt::Key_C, QObject::tr("Copy cell(s) contents to clipboard")) +// CFG_KEY_ENTRY(COPY_AS, Qt::CTRL + Qt::SHIFT + Qt::Key_C, QObject::tr("")) + CFG_KEY_ENTRY(PASTE, Qt::CTRL + Qt::Key_V, QObject::tr("Paste cell(s) contents from clipboard")) +// CFG_KEY_ENTRY(PASTE_AS, Qt::CTRL + Qt::SHIFT + Qt::Key_V, QObject::tr("")) + CFG_KEY_ENTRY(ERASE, Qt::ALT + Qt::Key_Backspace, QObject::tr("Set empty value to selected cell(s)")) + CFG_KEY_ENTRY(SET_NULL, Qt::Key_Backspace, QObject::tr("Set NULL value to selected cell(s)")) + CFG_KEY_ENTRY(COMMIT, Qt::CTRL + Qt::Key_Return, QObject::tr("Commit changes to cell(s) contents")) + CFG_KEY_ENTRY(ROLLBACK, Qt::CTRL + Qt::Key_Backspace, QObject::tr("Rollback changes to cell(s) contents")) + CFG_KEY_ENTRY(DELETE_ROW, Qt::Key_Delete, QObject::tr("Delete selected data row")) + CFG_KEY_ENTRY(INSERT_ROW, Qt::Key_Insert, QObject::tr("Insert new data row")) + CFG_KEY_ENTRY(OPEN_VALUE_EDITOR, Qt::ALT + Qt::Key_Return, QObject::tr("Open contents of selected cell in a separate editor")) +) + +class GUI_API_EXPORT SqlQueryView : public QTableView, public ExtActionContainer +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + COPY, + COPY_AS, + PASTE, + PASTE_AS, + SET_NULL, + ERASE, + ROLLBACK, + COMMIT, + INSERT_ROW, + INSERT_MULTIPLE_ROWS, + DELETE_ROW, + SELECTIVE_COMMIT, + SELECTIVE_ROLLBACK, + OPEN_VALUE_EDITOR, + SORT_DIALOG, + RESET_SORTING + }; + + enum ToolBar + { + }; + + explicit SqlQueryView(QWidget* parent = 0); + virtual ~SqlQueryView(); + QList getSelectedItems(); + SqlQueryItem* getCurrentItem(); + SqlQueryModel* getModel(); + void setModel(QAbstractItemModel *model); + SqlQueryItem *itemAt(const QPoint& pos); + QToolBar* getToolBar(int toolbar) const; + void addAdditionalAction(QAction* action); + QModelIndex getCurrentIndex() const; + + protected: + void mouseDoubleClickEvent(QMouseEvent* event); + + private: + void init(); + void setupWidgetCover(); + void createActions(); + void setupDefShortcuts(); + void refreshShortcuts(); + void setupActionsForMenu(SqlQueryItem* currentItem, const QList& selectedItems); + void setupHeaderMenu(); + bool handleDoubleClick(SqlQueryItem* item); + + SqlQueryItemDelegate* itemDelegate = nullptr; + QMenu* contextMenu = nullptr; + QMenu* headerContextMenu = nullptr; + WidgetCover* widgetCover = nullptr; + QPushButton* cancelButton = nullptr; + QProgressBar* busyBar = nullptr; + QList additionalActions; + + private slots: + void updateCommitRollbackActions(bool enabled); + void customContextMenuRequested(const QPoint& pos); + void headerContextMenuRequested(const QPoint& pos); + void openSortDialog(); + void resetSorting(); + void sortingUpdated(const QueryExecutor::SortList& sortOrder); + void updateFont(); + + public slots: + void executionStarted(); + void executionEnded(); + void setCurrentRow(int row); + void copy(); + void paste(); + void copyAs(); + void pasteAs(); + void setNull(); + void erase(); + void commit(); + void rollback(); + void selectiveCommit(); + void selectiveRollback(); + void openValueEditor(SqlQueryItem* item); + void openValueEditor(); + + signals: + void contextMenuRequested(SqlQueryItem* currentItem, const QList& selectedItems); + void requestForRowInsert(); + void requestForMultipleRowInsert(); + void requestForRowDelete(); +}; + +GUI_API_EXPORT int qHash(SqlQueryView::Action action); + +#endif // SQLQUERYVIEW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp new file mode 100644 index 0000000..c713737 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp @@ -0,0 +1,332 @@ +#include "sqltablemodel.h" +#include "common/utils_sql.h" +#include "sqlqueryitem.h" +#include "services/notifymanager.h" +#include +#include +#include + +SqlTableModel::SqlTableModel(QObject *parent) : + SqlQueryModel(parent) +{ +} + +QString SqlTableModel::getDatabase() const +{ + return database; +} + +QString SqlTableModel::getTable() const +{ + return table; +} + +void SqlTableModel::setDatabaseAndTable(const QString& database, const QString& table) +{ + this->database = database; + this->table = table; + setQuery("SELECT * FROM "+getDataSource()); + + SchemaResolver resolver(db); + isWithOutRowIdTable = resolver.isWithoutRowIdTable(database, table); +} + +SqlQueryModel::Features SqlTableModel::features() const +{ + return INSERT_ROW|DELETE_ROW|FILTERING; +} + + +bool SqlTableModel::commitAddedRow(const QList& itemsInRow) +{ + QList modelColumns = getTableColumnModels(table); + if (modelColumns.size() != itemsInRow.size()) + { + qCritical() << "Tried to SqlTableModel::commitAddedRow() with number of columns in argument different than model resolved for the table."; + return false; + } + + // Check that just in case: + if (modelColumns.size() == 0) + { + qCritical() << "Tried to SqlTableModel::commitAddedRow() with number of resolved columns in the table equal to 0!"; + return false; + } + + // Prepare column placeholders and their values + QStringList colNameList; + QStringList sqlValues; + QList args; + updateColumnsAndValues(itemsInRow, modelColumns, colNameList, sqlValues, args); + + // Prepare SQL query + QString sql = getInsertSql(modelColumns, colNameList, sqlValues, args); + + // Execute query + SqlQueryPtr result = db->exec(sql, args); + + // Handle error + if (result->isError()) + { + foreach (SqlQueryItem* item, itemsInRow) + item->setCommitingError(true); + + notifyError(tr("Error while commiting new row: %1").arg(result->getErrorText())); + return false; + } + + // Reloading row with actual values (because of DEFAULT, AUTOINCR) + RowId rowId = result->getInsertRowId(); + updateRowAfterInsert(itemsInRow, modelColumns, rowId); + return true; +} + +bool SqlTableModel::commitDeletedRow(const QList& itemsInRow) +{ + if (itemsInRow.size() == 0) + { + qCritical() << "Tried to SqlTableModel::commitDeletedRow() with number of items equal to 0!"; + return false; + } + + if (itemsInRow[0]->isJustInsertedWithOutRowId()) + { + QString msg = tr("When inserted new row to the WITHOUT ROWID table, using DEFAULT value for PRIMARY KEY, " + "the table has to be reloaded in order to delete the new row."); + notifyError(tr("Error while deleting row from table %1: %2").arg(table).arg(msg)); + return false; + } + + RowId rowId = itemsInRow[0]->getRowId(); + if (rowId.isEmpty()) + return false; + + Dialect dialect = db->getDialect(); + + CommitDeleteQueryBuilder queryBuilder; + queryBuilder.setTable(wrapObjIfNeeded(table, dialect)); + queryBuilder.setRowId(rowId); + + QString sql = queryBuilder.build(); + QHash args = queryBuilder.getQueryArgs(); + + SqlQueryPtr result = db->exec(sql, args); + if (result->isError()) + { + notifyError(tr("Error while deleting row from table %1: %2").arg(table).arg(result->getErrorText())); + return false; + } + + if (!SqlQueryModel::commitDeletedRow(itemsInRow)) + qCritical() << "Could not delete row from SqlQueryView while commiting row deletion."; + + return true; +} + +void SqlTableModel::applySqlFilter(const QString& value) +{ + if (value.isEmpty()) + { + resetFilter(); + return; + } + + setQuery("SELECT * FROM "+getDataSource()+" WHERE "+value); + executeQuery(); +} + +void SqlTableModel::applyStringFilter(const QString& value) +{ + if (value.isEmpty()) + { + resetFilter(); + return; + } + + Dialect dialect = db->getDialect(); + QStringList conditions; + foreach (SqlQueryModelColumnPtr column, columns) + conditions << wrapObjIfNeeded(column->column, dialect)+" LIKE '%"+value+"%'"; + + setQuery("SELECT * FROM "+getDataSource()+" WHERE "+conditions.join(" OR ")); + executeQuery(); +} + +void SqlTableModel::applyRegExpFilter(const QString& value) +{ + if (value.isEmpty()) + { + resetFilter(); + return; + } + + Dialect dialect = db->getDialect(); + QStringList conditions; + foreach (SqlQueryModelColumnPtr column, columns) + conditions << wrapObjIfNeeded(column->column, dialect)+" REGEXP '"+value+"'"; + + setQuery("SELECT * FROM "+getDataSource()+" WHERE "+conditions.join(" OR ")); + executeQuery(); +} + +void SqlTableModel::resetFilter() +{ + setQuery("SELECT * FROM "+getDataSource()); + //reload(); + executeQuery(); +} + +void SqlTableModel::updateRowAfterInsert(const QList& itemsInRow, const QList& modelColumns, RowId rowId) +{ + // Update cells with data just like it was entered. Only DEFAULT and PRIMARY KEY AUTOINCREMENT will have special values. + QList values; + SqlQueryItem* item = nullptr; + int i = 0; + for (const SqlQueryModelColumnPtr& modelColumn : modelColumns) + { + item = itemsInRow[i++]; +// qDebug() << "Item is for column" << item->getColumn()->column << ", column iterated:" << modelColumn->column; + if (item->getValue().isNull()) + { + if (modelColumn->isDefault()) + { + values << modelColumn->getDefaultConstraint()->defaultValue; + continue; + } + + // If this is the PK AUTOINCR column we use RowId as value, because it was skipped when setting values to items + if (modelColumn->isPk() && modelColumn->isAutoIncr()) + { + values << rowId["ROWID"]; + continue; + } + } + + values << item->getValue(); + } + + // Update cell data with results + int colIdx = 0; + for (SqlQueryItem* itemToUpdate : itemsInRow) + { + updateItem(itemToUpdate, values[colIdx], colIdx, rowId); + + if (isWithOutRowIdTable && rowId.isEmpty()) + itemToUpdate->setJustInsertedWithOutRowId(true); + + colIdx++; + } +} + +QString SqlTableModel::getDatabasePrefix() +{ + if (database.isNull()) + return "main."; + + return wrapObjIfNeeded(database, db->getDialect()) + "."; +} + +QString SqlTableModel::getDataSource() +{ + return getDatabasePrefix() + wrapObjIfNeeded(table, db->getDialect()); +} + +QString SqlTableModel::getInsertSql(const QList& modelColumns, QStringList& colNameList, + QStringList& sqlValues, QList& args) +{ + Dialect dialect = db->getDialect(); + + QString sql = "INSERT INTO "+wrapObjIfNeeded(table, dialect); + if (colNameList.size() == 0) + { + // There are all null values passed to the query. We need to use Sqlite3 special syntax, or find at least one default value + if (dialect == Dialect::Sqlite2) + updateColumnsAndValuesWithDefaultValues(modelColumns, colNameList, sqlValues, args); + else // Sqlite3 has default values syntax for that case + sql += " DEFAULT VALUES"; + } + else + sql += " ("+colNameList.join(", ")+") VALUES ("+sqlValues.join(", ")+")"; + + return sql; +} + +void SqlTableModel::updateColumnsAndValues(const QList& itemsInRow, const QList& modelColumns, + QStringList& colNameList, QStringList& sqlValues, QList& args) +{ + Dialect dialect = db->getDialect(); + + SqlQueryItem* item = nullptr; + int i = 0; + foreach (SqlQueryModelColumnPtr modelColumn, modelColumns) + { + item = itemsInRow[i++]; + if (item->getValue().isNull()) + { + if (modelColumn->isDefault()) + continue; + + if (modelColumn->isPk() && modelColumn->isAutoIncr()) + continue; + } + + colNameList << wrapObjIfNeeded(modelColumn->column, dialect); + sqlValues << ":arg" + QString::number(i); + args << item->getFullValue(); + } +} + +void SqlTableModel::updateColumnsAndValuesWithDefaultValues(const QList& modelColumns, QStringList& colNameList, + QStringList& sqlValues, QList& args) +{ + Dialect dialect = db->getDialect(); + + // First try to find the one with DEFAULT value + foreach (SqlQueryModelColumnPtr modelColumn, modelColumns) + { + if (modelColumn->isDefault()) + { + colNameList << wrapObjIfNeeded(modelColumn->column, dialect); + sqlValues << ":defValue"; + args << modelColumn->getDefaultConstraint()->defaultValue; + return; + } + } + + // No DEFAULT, try with AUTOINCR + foreach (SqlQueryModelColumnPtr modelColumn, modelColumns) + { + if (modelColumn->isPk() && modelColumn->isAutoIncr()) + { + QString colName = wrapObjIfNeeded(modelColumn->column, dialect); + QString tableName = wrapObjIfNeeded(table, dialect); + SqlQueryPtr results = db->exec("SELECT max("+colName+") FROM "+tableName); + int rowid = 0; + QVariant cellValue = results->getSingleCell(); + if (!cellValue.isNull()) + rowid = cellValue.toInt(); + + colNameList << wrapObjIfNeeded(modelColumn->column, dialect); + sqlValues << ":defValue"; + args << rowid; + return; + } + } + + // No luck with AUTOINCR either, put NULL and if there's a NOT NULL in any column, + // user will get the proper error message from Sqlite. + colNameList << wrapObjIfNeeded(modelColumns[0]->column, dialect); + sqlValues << ":defValue"; + args << QVariant(); +} + +QString SqlTableModel::CommitDeleteQueryBuilder::build() +{ + QString dbAndTable; + if (!database.isNull()) + dbAndTable += database+"."; + + dbAndTable += table; + QString conditions = RowIdConditionBuilder::build(); + return "DELETE FROM "+dbAndTable+" WHERE "+conditions+";"; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h new file mode 100644 index 0000000..6adb5b6 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.h @@ -0,0 +1,50 @@ +#ifndef SQLTABLEMODEL_H +#define SQLTABLEMODEL_H + +#include "guiSQLiteStudio_global.h" +#include "sqlquerymodel.h" + +class GUI_API_EXPORT SqlTableModel : public SqlQueryModel +{ + Q_OBJECT + public: + explicit SqlTableModel(QObject *parent = 0); + + QString getDatabase() const; + QString getTable() const; + void setDatabaseAndTable(const QString& database, const QString& table); + + Features features() const; + void applySqlFilter(const QString& value); + void applyStringFilter(const QString& value); + void applyRegExpFilter(const QString& value); + void resetFilter(); + + protected: + bool commitAddedRow(const QList& itemsInRow); + bool commitDeletedRow(const QList& itemsInRow); + + private: + class CommitDeleteQueryBuilder : public CommitUpdateQueryBuilder + { + public: + QString build(); + }; + + + void updateColumnsAndValuesWithDefaultValues(const QList& modelColumns, QStringList& colNameList, + QStringList& sqlValues, QList& args); + void updateColumnsAndValues(const QList& itemsInRow, const QList& modelColumns, + QStringList& colNameList, QStringList& sqlValues, QList& args); + QString getInsertSql(const QList& modelColumns, QStringList& colNameList, QStringList& sqlValues, + QList& args); + void updateRowAfterInsert(const QList& itemsInRow, const QList& modelColumns, RowId rowId); + QString getDatabasePrefix(); + QString getDataSource(); + + QString table; + QString database; + bool isWithOutRowIdTable = false; +}; + +#endif // SQLTABLEMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dataview.cpp b/SQLiteStudio3/guiSQLiteStudio/dataview.cpp new file mode 100644 index 0000000..c7f615a --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dataview.cpp @@ -0,0 +1,836 @@ +#include "dataview.h" +#include "datagrid/sqltablemodel.h" +#include "datagrid/sqlquerymodel.h" +#include "datagrid/sqlqueryview.h" +#include "formview.h" +#include "common/extlineedit.h" +#include "mainwindow.h" +#include "statusfield.h" +#include "common/intvalidator.h" +#include "common/extaction.h" +#include "iconmanager.h" +#include "uiconfig.h" +#include "datagrid/sqlqueryitem.h" +#include +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(DataView) +DataView::FilterMode DataView::filterMode; +DataView::TabsPosition DataView::tabsPosition; +QHash DataView::staticActions; +QHash DataView::staticActionGroups; + +DataView::DataView(QWidget *parent) : + QTabWidget(parent) +{ +} + +void DataView::init(SqlQueryModel* model) +{ + createContents(); + + this->model = model; + this->model->setView(gridView); + + rowCountLabel = new QLabel(); + formViewRowCountLabel = new QLabel(); + formViewCurrentRowLabel = new QLabel(); + + initFormView(); + initPageEdit(); + initFilter(); + initActions(); + initUpdates(); + initSlots(); + updateTabsMode(); +} + +void DataView::initUpdates() +{ + updatePageEdit(); + updateFormNavigationState(); + updateGridNavigationState(); + updateCommitRollbackActions(false); +} + +void DataView::initSlots() +{ + connect(model, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful())); + connect(model, SIGNAL(loadingEnded(bool)), this, SLOT(dataLoadingEnded(bool))); + connect(model, SIGNAL(commitStatusChanged(bool)), this, SLOT(updateCommitRollbackActions(bool))); + connect(gridView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + model, SLOT(updateSelectiveCommitRollbackActions(QItemSelection,QItemSelection))); + connect(model, SIGNAL(selectiveCommitStatusChanged(bool)), this, SLOT(updateSelectiveCommitRollbackActions(bool))); + connect(model, SIGNAL(executionStarted()), gridView, SLOT(executionStarted())); + connect(model, SIGNAL(loadingEnded(bool)), gridView, SLOT(executionEnded())); + connect(model, SIGNAL(totalRowsAndPagesAvailable()), this, SLOT(totalRowsAndPagesAvailable())); + connect(gridView->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(columnsHeaderClicked(int))); + connect(this, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); +} + +void DataView::initFormView() +{ + formView = new FormView(); + formWidget->layout()->addWidget(formView); + formView->setModel(model); + formView->setGridView(gridView); + connect(formView, SIGNAL(commitStatusChanged()), this, SLOT(updateFormCommitRollbackActions())); + connect(formView, SIGNAL(currentRowChanged()), this, SLOT(updateFormNavigationState())); + updateCurrentFormViewRow(); +} + +void DataView::initFilter() +{ + filterEdit = new ExtLineEdit(); + filterEdit->setExpandingMinWidth(100); + filterEdit->setExpandingMaxWidth(200); + filterEdit->setExpanding(true); + filterEdit->setClearButtonEnabled(true); + filterEdit->setPlaceholderText(tr("Filter data", "data view")); + connect(filterEdit, SIGNAL(valueErased()), this, SLOT(resetFilter())); + connect(filterEdit, SIGNAL(returnPressed()), this, SLOT(applyFilter())); +} + +void DataView::createContents() +{ + gridWidget = new QWidget(); + formWidget = new QWidget(); + addTab(gridWidget, tr("Grid view")); + addTab(formWidget, tr("Form view")); + + QVBoxLayout* vbox = new QVBoxLayout(); + gridWidget->setLayout(vbox); + + vbox = new QVBoxLayout(); + formWidget->setLayout(vbox); + + gridToolBar = new QToolBar(); + formToolBar = new QToolBar(); + gridWidget->layout()->addWidget(gridToolBar); + formWidget->layout()->addWidget(formToolBar); + +#ifdef Q_OS_MACX + QStyle *fusion = QStyleFactory::create("Fusion"); + gridToolBar->setStyle(fusion); + formToolBar->setStyle(fusion); +#endif + + gridView = new SqlQueryView(); + gridView->setCornerButtonEnabled(true); + gridView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + gridWidget->layout()->addWidget(gridView); +} + +void DataView::initPageEdit() +{ + pageEdit = new ExtLineEdit(); + pageValidator = new IntValidator(1, 1, pageEdit); + pageValidator->setDefaultValue(1); + pageEdit->setAlignment(Qt::AlignCenter); + pageEdit->setValidator(pageValidator); + pageEdit->setExpanding(true); + pageEdit->setExpandingMinWidth(20); + connect(pageEdit, SIGNAL(editingFinished()), this, SLOT(pageEntered())); +} + +void DataView::createActions() +{ + bool rowInserting = model->features().testFlag(SqlQueryModel::INSERT_ROW); + bool rowDeleting = model->features().testFlag(SqlQueryModel::DELETE_ROW); + + // Grid actions + createAction(REFRESH_DATA, ICONS.RELOAD, tr("Refresh table data", "data view"), this, SLOT(refreshData()), gridToolBar, gridView); + gridToolBar->addSeparator(); + if (rowInserting) + { + gridToolBar->addAction(gridView->getAction(SqlQueryView::INSERT_ROW)); + attachActionInMenu(gridView->getAction(SqlQueryView::INSERT_ROW), gridView->getAction(SqlQueryView::INSERT_MULTIPLE_ROWS), gridToolBar); + } + + if (rowDeleting) + gridToolBar->addAction(gridView->getAction(SqlQueryView::DELETE_ROW)); + + gridToolBar->addAction(gridView->getAction(SqlQueryView::COMMIT)); + gridToolBar->addAction(gridView->getAction(SqlQueryView::ROLLBACK)); + gridToolBar->addSeparator(); + createAction(FIRST_PAGE, ICONS.PAGE_FIRST, tr("First page", "data view"), this, SLOT(firstPage()), gridToolBar); + createAction(PREV_PAGE, ICONS.PAGE_PREV, tr("Previous page", "data view"), this, SLOT(prevPage()), gridToolBar); + actionMap[PAGE_EDIT] = gridToolBar->addWidget(pageEdit); + createAction(NEXT_PAGE, ICONS.PAGE_NEXT, tr("Next page", "data view"), this, SLOT(nextPage()), gridToolBar); + createAction(LAST_PAGE, ICONS.PAGE_LAST, tr("Last page", "data view"), this, SLOT(lastPage()), gridToolBar); + gridToolBar->addSeparator(); + if (model->features().testFlag(SqlQueryModel::FILTERING)) + { + actionMap[FILTER_VALUE] = gridToolBar->addWidget(filterEdit); + createAction(FILTER, tr("Apply filter", "data view"), this, SLOT(applyFilter()), gridToolBar); + attachActionInMenu(FILTER, staticActions[FILTER_STRING], gridToolBar); + attachActionInMenu(FILTER, staticActions[FILTER_REGEXP], gridToolBar); + attachActionInMenu(FILTER, staticActions[FILTER_SQL], gridToolBar); + gridToolBar->addSeparator(); + updateFilterIcon(); + + connect(staticActions[FILTER_STRING], SIGNAL(triggered()), this, SLOT(filterModeSelected())); + connect(staticActions[FILTER_REGEXP], SIGNAL(triggered()), this, SLOT(filterModeSelected())); + connect(staticActions[FILTER_SQL], SIGNAL(triggered()), this, SLOT(filterModeSelected())); + } + actionMap[GRID_TOTAL_ROWS] = gridToolBar->addWidget(rowCountLabel); + + noConfigShortcutActions << GRID_TOTAL_ROWS << FILTER_VALUE; + + createAction(SELECTIVE_COMMIT, ICONS.COMMIT, tr("Commit changes for selected cells", "data view"), this, SLOT(selectiveCommitGrid()), this); + createAction(SELECTIVE_ROLLBACK, ICONS.ROLLBACK, tr("Rollback changes for selected cells", "data view"), this, SLOT(selectiveRollbackGrid()), this); + createAction(SHOW_GRID_VIEW, tr("Show grid view of results", "sql editor"), this, SLOT(showGridView()), this); + createAction(SHOW_FORM_VIEW, tr("Show form view of results", "sql editor"), this, SLOT(showFormView()), this); + + connect(gridView, SIGNAL(requestForRowInsert()), this, SLOT(insertRow())); + connect(gridView, SIGNAL(requestForMultipleRowInsert()), this, SLOT(insertMultipleRows())); + connect(gridView, SIGNAL(requestForRowDelete()), this, SLOT(deleteRow())); + + + // Form view actions + if (rowInserting) + formToolBar->addAction(formView->getAction(FormView::INSERT_ROW)); + + if (rowDeleting) + formToolBar->addAction(formView->getAction(FormView::DELETE_ROW)); + + if (rowInserting || rowDeleting) + formToolBar->addSeparator(); + + formToolBar->addAction(formView->getAction(FormView::COMMIT)); + formToolBar->addAction(formView->getAction(FormView::ROLLBACK)); + formToolBar->addSeparator(); + formToolBar->addAction(formView->getAction(FormView::FIRST_ROW)); + formToolBar->addAction(formView->getAction(FormView::PREV_ROW)); + formToolBar->addAction(formView->getAction(FormView::NEXT_ROW)); + formToolBar->addAction(formView->getAction(FormView::LAST_ROW)); + formToolBar->addSeparator(); + actionMap[FORM_TOTAL_ROWS] = formToolBar->addWidget(formViewRowCountLabel); + formToolBar->addSeparator(); + actionMap[FORM_CURRENT_ROW] = formToolBar->addWidget(formViewCurrentRowLabel); + + noConfigShortcutActions << FORM_TOTAL_ROWS; + + connect(formView, SIGNAL(requestForCommit()), this, SLOT(commitForm())); + connect(formView, SIGNAL(requestForRollback()), this, SLOT(rollbackForm())); + connect(formView, SIGNAL(requestForFirstRow()), this, SLOT(firstRow())); + connect(formView, SIGNAL(requestForPrevRow()), this, SLOT(prevRow())); + connect(formView, SIGNAL(requestForNextRow()), this, SLOT(nextRow())); + connect(formView, SIGNAL(requestForLastRow()), this, SLOT(lastRow())); + connect(formView, SIGNAL(requestForRowInsert()), this, SLOT(insertRow())); + connect(formView, SIGNAL(requestForRowDelete()), this, SLOT(deleteRow())); + + // Actions for grid menu only + gridView->addAdditionalAction(staticActions[TABS_ON_TOP]); + gridView->addAdditionalAction(staticActions[TABS_AT_BOTTOM]); + connect(staticActions[TABS_ON_TOP], SIGNAL(triggered()), this, SLOT(updateTabsMode())); + connect(staticActions[TABS_AT_BOTTOM], SIGNAL(triggered()), this, SLOT(updateTabsMode())); +} + +void DataView::setupDefShortcuts() +{ + // Widget context + setShortcutContext({REFRESH_DATA, SHOW_GRID_VIEW, SHOW_FORM_VIEW}, Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(DataView, Action); +} + +void DataView::createStaticActions() +{ + // Filtering actions + staticActions[FILTER_STRING] = new ExtAction(ICONS.APPLY_FILTER_TXT, tr("Filter by text", "data view"), MainWindow::getInstance()); + staticActions[FILTER_REGEXP] = new ExtAction(ICONS.APPLY_FILTER_RE, tr("Filter by the Regular Expression", "data view"), MainWindow::getInstance()); + staticActions[FILTER_SQL] = new ExtAction(ICONS.APPLY_FILTER_SQL, tr("Filter by SQL expression", "data view"), MainWindow::getInstance()); + + staticActionGroups[ActionGroup::FILTER_MODE] = new QActionGroup(MainWindow::getInstance()); + staticActionGroups[ActionGroup::FILTER_MODE]->addAction(staticActions[FILTER_STRING]); + staticActionGroups[ActionGroup::FILTER_MODE]->addAction(staticActions[FILTER_SQL]); + staticActionGroups[ActionGroup::FILTER_MODE]->addAction(staticActions[FILTER_REGEXP]); + + connect(staticActions[FILTER_STRING], &QAction::triggered, [=]() + { + filterMode = FilterMode::STRING; + }); + connect(staticActions[FILTER_SQL], &QAction::triggered, [=]() + { + filterMode = FilterMode::SQL; + }); + connect(staticActions[FILTER_REGEXP], &QAction::triggered, [=]() + { + filterMode = FilterMode::REGEXP; + }); + + staticActions[FILTER_STRING]->setCheckable(true); + staticActions[FILTER_REGEXP]->setCheckable(true); + staticActions[FILTER_SQL]->setCheckable(true); + if (filterMode == FilterMode::STRING) + staticActions[FILTER_STRING]->setChecked(true); + else if (filterMode == FilterMode::REGEXP) + staticActions[FILTER_REGEXP]->setChecked(true); + else + staticActions[FILTER_SQL]->setChecked(true); + + // Tabs position actions + staticActions[TABS_ON_TOP] = new ExtAction(ICONS.TABS_ON_TOP, tr("Tabs on top", "data view"), MainWindow::getInstance()); + staticActions[TABS_AT_BOTTOM] = new ExtAction(ICONS.TABS_AT_BOTTOM, tr("Tabs at bottom", "data view"), MainWindow::getInstance()); + + staticActionGroups[ActionGroup::TABS_POSITION] = new QActionGroup(MainWindow::getInstance()); + staticActionGroups[ActionGroup::TABS_POSITION]->addAction(staticActions[TABS_ON_TOP]); + staticActionGroups[ActionGroup::TABS_POSITION]->addAction(staticActions[TABS_AT_BOTTOM]); + + connect(staticActions[TABS_ON_TOP], &QAction::triggered, [=]() + { + tabsPosition = TabsPosition::TOP; + CFG_UI.General.DataViewTabs.set("TOP"); + }); + connect(staticActions[TABS_AT_BOTTOM], &QAction::triggered, [=]() + { + tabsPosition = TabsPosition::BOTTOM; + CFG_UI.General.DataViewTabs.set("BOTTOM"); + }); + + staticActions[TABS_ON_TOP]->setCheckable(true); + staticActions[TABS_AT_BOTTOM]->setCheckable(true); + if (tabsPosition == TabsPosition::TOP) + staticActions[TABS_ON_TOP]->setChecked(true); + else + staticActions[TABS_AT_BOTTOM]->setChecked(true); + +} + +void DataView::loadTabsMode() +{ + QString valString = CFG_UI.General.DataViewTabs.get(); + if (valString == "TOP") + tabsPosition = TabsPosition::TOP; + else if (valString == "BOTTOM") + tabsPosition = TabsPosition::BOTTOM; +} + +void DataView::goToFormRow(IndexModifier idxMod) +{ + if (formView->isModified()) + formView->copyDataToGrid(); + + int row = gridView->getCurrentIndex().row(); + + switch (idxMod) + { + case IndexModifier::FIRST: + row = 0; + break; + case IndexModifier::PREV: + row--; + break; + case IndexModifier::NEXT: + row++; + break; + case IndexModifier::LAST: + row = model->rowCount() - 1; + break; + } + + QModelIndex newRowIdx = model->index(row, 0); + if (!newRowIdx.isValid()) + return; + + gridView->setCurrentIndex(newRowIdx); + model->loadFullDataForEntireRow(row); + formView->updateFromGrid(); + updateCurrentFormViewRow(); +} + +void DataView::setNavigationState(bool enabled) +{ + navigationState = enabled; + updateNavigationState(); + setFormViewEnabled(enabled); +} + +void DataView::updateNavigationState() +{ + updateGridNavigationState(); + updateFormNavigationState(); +} + +void DataView::updateGridNavigationState() +{ + int page = model->getCurrentPage(); + bool prevResultsAvailable = page > 0; + bool nextResultsAvailable = (page + 1) < model->getTotalPages(); + bool reloadResultsAvailable = model->canReload(); + bool pageNumEditAvailable = (prevResultsAvailable || nextResultsAvailable); + + actionMap[PAGE_EDIT]->setEnabled(navigationState && totalPagesAvailable && pageNumEditAvailable); + actionMap[REFRESH_DATA]->setEnabled(navigationState && reloadResultsAvailable); + actionMap[NEXT_PAGE]->setEnabled(navigationState && totalPagesAvailable && nextResultsAvailable); + actionMap[LAST_PAGE]->setEnabled(navigationState && totalPagesAvailable && nextResultsAvailable); + actionMap[PREV_PAGE]->setEnabled(navigationState && totalPagesAvailable && prevResultsAvailable); + actionMap[FIRST_PAGE]->setEnabled(navigationState && totalPagesAvailable && prevResultsAvailable); +} + +void DataView::updateFormNavigationState() +{ + int row = gridView->getCurrentIndex().row(); + int lastRow = model->rowCount() - 1; + bool nextRowAvailable = row < lastRow; + bool prevRowAvailable = row > 0; + + formView->getAction(FormView::NEXT_ROW)->setEnabled(navigationState && nextRowAvailable); + formView->getAction(FormView::PREV_ROW)->setEnabled(navigationState && prevRowAvailable); + + // We changed row in form view, this one might be already modified and be capable for commit/rollback + updateFormCommitRollbackActions(); +} + +void DataView::updateFormCommitRollbackActions() +{ + bool enabled = formView->isModified(); + formView->getAction(FormView::COMMIT)->setEnabled(enabled); + formView->getAction(FormView::ROLLBACK)->setEnabled(enabled); + uncommittedForm = enabled; +} + +void DataView::showGridView() +{ + setCurrentIndex(0); +} + +void DataView::showFormView() +{ + setCurrentIndex(1); + updateCurrentFormViewRow(); +} + +void DataView::updateTabsMode() +{ + switch (tabsPosition) + { + case DataView::TabsPosition::TOP: + setTabPosition(TabPosition::North); + break; + case DataView::TabsPosition::BOTTOM: + setTabPosition(TabPosition::South); + break; + } +} + +void DataView::filterModeSelected() +{ + QAction* modeAction = dynamic_cast(sender()); + actionMap[FILTER]->setIcon(modeAction->icon()); +} + +void DataView::updateCommitRollbackActions(bool enabled) +{ + gridView->getAction(SqlQueryView::COMMIT)->setEnabled(enabled); + gridView->getAction(SqlQueryView::ROLLBACK)->setEnabled(enabled); + uncommittedGrid = enabled; +} + +void DataView::updateSelectiveCommitRollbackActions(bool enabled) +{ + actionMap[SELECTIVE_COMMIT]->setEnabled(enabled); + actionMap[SELECTIVE_ROLLBACK]->setEnabled(enabled); +} + +void DataView::goToPage(const QString& pageStr) +{ + bool ok; + int page = pageStr.toInt(&ok); + if (!ok) + return; + + page--; // Converting from visual page representation to logical + + // We need to get this synchronized against event loop, cause changeing action status (probably) calls event loop update, + // so this method was sometimes called twice at the time (until setResultsNavigationState() call below), + // but the page in results model wasn't updated yet. We cannot simply move setResultsNavigationState() below gotoPage(), + // because we need to disable actions, before model returns from execution, so it can re-enable those actions. + // This method was called twice, because QLineEdit::editionFinished (the filter) signal is emitted twice: + // - because enter was pressed + // - because edit lost its focus (which happens after enter was hit), at least it looks like it + if (!manualPageChangeMutex.tryLock()) + return; + + if (page == model->getCurrentPage(true)) + return; + + setNavigationState(false); + model->gotoPage(page); + manualPageChangeMutex.unlock(); +} + +void DataView::updatePageEdit() +{ + int page = model->getCurrentPage()+1; + QString text = QString::number(page); + int totalPages = model->getTotalPages(); + pageEdit->setText(text); + pageEdit->setToolTip(QObject::tr("Total pages available: %1").arg(totalPages)); + pageValidator->setTop(totalPages); + pageValidator->setDefaultValue(page); + updateCurrentFormViewRow(); +} + +void DataView::updateResultsCount(int resultsCount) +{ + if (resultsCount >= 0) + { + QString msg = QObject::tr("Total rows loaded: %1").arg(resultsCount); + rowCountLabel->setText(msg); + formViewRowCountLabel->setText(msg); + rowCountLabel->setToolTip(QString::null); + formViewRowCountLabel->setToolTip(QString::null); + } + else + { + rowCountLabel->setText(" "); // this might seem weird, but if it's not a wide, whitespace string, then icon is truncated from right side + formViewRowCountLabel->setText(" "); + rowCountLabel->setMovie(ICONS.LOADING); + formViewRowCountLabel->setMovie(ICONS.LOADING); + + static QString loadingMsg = tr("Total number of rows is being counted.\nBrowsing other pages will be possible after the row counting is done."); + rowCountLabel->setToolTip(loadingMsg); + formViewRowCountLabel->setToolTip(loadingMsg); + } +} + +void DataView::updateCurrentFormViewRow() +{ + int rowsPerPage = CFG_UI.General.NumberOfRowsPerPage.get(); + int page = gridView->getModel()->getCurrentPage(); + int row = rowsPerPage * page + 1 + gridView->getCurrentIndex().row(); + formViewCurrentRowLabel->setText(tr("Row: %1").arg(row)); +} + +void DataView::setFormViewEnabled(bool enabled) +{ + if (!enabled) + setCurrentIndex(0); + + setTabEnabled(1, enabled); +} + +void DataView::readData() +{ + setNavigationState(false); + model->executeQuery(); +} + +void DataView::updateFilterIcon() +{ + for (Action act : {FILTER_STRING, FILTER_SQL, FILTER_REGEXP}) + { + if (staticActions[act]->isChecked()) + { + actionMap[FILTER]->setIcon(staticActions[act]->icon()); + break; + } + } +} + +bool DataView::isUncommited() const +{ + return uncommittedGrid || uncommittedForm; +} + +void DataView::dataLoadingEnded(bool successful) +{ + if (successful) + updatePageEdit(); + + setNavigationState(true); +} + +void DataView::executionSuccessful() +{ + updateResultsCount(-1); +} + +void DataView::totalRowsAndPagesAvailable() +{ + updateResultsCount(model->getTotalRowsReturned()); + totalPagesAvailable = true; + updatePageEdit(); + updateNavigationState(); +} + +void DataView::refreshData() +{ + totalPagesAvailable = false; + readData(); +} + +void DataView::insertRow() +{ + if (!model->features().testFlag(SqlQueryModel::INSERT_ROW)) + return; + + model->addNewRow(); + initFormViewForNewRow(); + formView->updateFromGrid(); + updateCurrentFormViewRow(); + formViewFocusFirstEditor(); +} + +void DataView::insertMultipleRows() +{ + if (!model->features().testFlag(SqlQueryModel::INSERT_ROW)) + return; + + model->addMultipleRows(); + formView->updateFromGrid(); + updateCurrentFormViewRow(); + formViewFocusFirstEditor(); +} + +void DataView::deleteRow() +{ + if (!model->features().testFlag(SqlQueryModel::DELETE_ROW)) + return; + + model->deleteSelectedRows(); + formView->updateFromGrid(); + updateCurrentFormViewRow(); + formViewFocusFirstEditor(); +} + +void DataView::commitGrid() +{ + model->commit(); +} + +void DataView::rollbackGrid() +{ + model->rollback(); +} + +void DataView::selectiveCommitGrid() +{ + QList selectedItems = gridView->getSelectedItems(); + model->commit(selectedItems); +} + +void DataView::selectiveRollbackGrid() +{ + QList selectedItems = gridView->getSelectedItems(); + model->rollback(selectedItems); +} + +void DataView::firstPage() +{ + setNavigationState(false); + model->firstPage(); +} + +void DataView::prevPage() +{ + setNavigationState(false); + model->prevPage(); +} + +void DataView::nextPage() +{ + setNavigationState(false); + model->nextPage(); +} + +void DataView::lastPage() +{ + setNavigationState(false); + model->lastPage(); +} + +void DataView::pageEntered() +{ + goToPage(pageEdit->text()); +} + +void DataView::applyFilter() +{ + if (!model->features().testFlag(SqlQueryModel::Feature::FILTERING)) + { + qWarning() << "Tried to apply filter on model that doesn't support it."; + return; + } + + QString value = filterEdit->text(); + switch (filterMode) + { + case DataView::FilterMode::STRING: + model->applyStringFilter(value); + break; + case DataView::FilterMode::SQL: + model->applySqlFilter(value); + break; + case DataView::FilterMode::REGEXP: + model->applyRegExpFilter(value); + break; + } +} + +void DataView::resetFilter() +{ + if (!model->features().testFlag(SqlQueryModel::Feature::FILTERING)) + { + qWarning() << "Tried to reset filter on model that doesn't support it."; + return; + } + + model->resetFilter(); +} + +void DataView::commitForm() +{ + formView->copyDataToGrid(); + gridView->selectRow(formView->getCurrentRow()); + selectiveCommitGrid(); + formView->updateFromGrid(); + formViewFocusFirstEditor(); +} + +void DataView::rollbackForm() +{ + formView->copyDataToGrid(); + gridView->selectRow(formView->getCurrentRow()); + selectiveRollbackGrid(); + formView->updateFromGrid(); + updateCurrentFormViewRow(); + formViewFocusFirstEditor(); +} + +void DataView::firstRow() +{ + goToFormRow(IndexModifier::FIRST); + formViewFocusFirstEditor(); +} + +void DataView::prevRow() +{ + goToFormRow(IndexModifier::PREV); + formViewFocusFirstEditor(); +} + +void DataView::nextRow() +{ + goToFormRow(IndexModifier::NEXT); + formViewFocusFirstEditor(); +} + +void DataView::lastRow() +{ + goToFormRow(IndexModifier::LAST); + formViewFocusFirstEditor(); +} + +void DataView::initFormViewForNewRow() +{ + if (currentWidget() != formWidget) + return; + + int row = gridView->getCurrentIndex().row(); + for (SqlQueryItem* item : getModel()->getRow(row)) + item->setValue(""); +} + +void DataView::formViewFocusFirstEditor() +{ + if (currentWidget() == formWidget) + formView->focusFirstEditor(); +} + +void DataView::columnsHeaderClicked(int columnIdx) +{ + model->changeSorting(columnIdx); +} + +void DataView::tabChanged(int newIndex) +{ + switch (newIndex) + { + case 0: + { + formView->copyDataToGrid(); + gridView->setFocus(); + break; + } + case 1: + { + if (!gridView->getCurrentIndex().isValid() && model->rowCount() > 0) + gridView->setCurrentRow(0); + + int row = gridView->getCurrentIndex().row(); + model->loadFullDataForEntireRow(row); + formView->updateFromGrid(); + updateCurrentFormViewRow(); + break; + } + } +} + +FormView* DataView::getFormView() const +{ + return formView; +} + +SqlQueryModel* DataView::getModel() const +{ + return model; +} + +QToolBar* DataView::getToolBar(int toolbar) const +{ + switch (static_cast(toolbar)) + { + case TOOLBAR_GRID: + return gridToolBar; + case TOOLBAR_FORM: + return formToolBar; + } + return nullptr; +} + +void DataView::staticInit() +{ + filterMode = FilterMode::STRING; + tabsPosition = TabsPosition::TOP; + loadTabsMode(); + createStaticActions(); +} + +void DataView::insertAction(ExtActionPrototype* action, DataView::ToolBar toolbar) +{ + return ExtActionContainer::insertAction(action, toolbar); +} + +void DataView::insertActionBefore(ExtActionPrototype* action, DataView::Action beforeAction, DataView::ToolBar toolbar) +{ + return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); +} + +void DataView::insertActionAfter(ExtActionPrototype* action, DataView::Action afterAction, DataView::ToolBar toolbar) +{ + return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); +} + +void DataView::removeAction(ExtActionPrototype* action, DataView::ToolBar toolbar) +{ + ExtActionContainer::removeAction(action, toolbar); +} + +SqlQueryView* DataView::getGridView() const +{ + return gridView; +} + +int qHash(DataView::ActionGroup action) +{ + return static_cast(action); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dataview.h b/SQLiteStudio3/guiSQLiteStudio/dataview.h new file mode 100644 index 0000000..19e56c0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dataview.h @@ -0,0 +1,199 @@ +#ifndef DATAVIEW_H +#define DATAVIEW_H + +#include "common/extactioncontainer.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class QToolBar; +class SqlQueryView; +class SqlQueryModel; +class FormView; +class ExtLineEdit; +class QLabel; +class IntValidator; + +CFG_KEY_LIST(DataView, QObject::tr("Data view (both grid and form)"), + CFG_KEY_ENTRY(REFRESH_DATA, Qt::Key_F5, QObject::tr("Refresh data")) + CFG_KEY_ENTRY(SHOW_GRID_VIEW, Qt::CTRL + Qt::Key_Comma, QObject::tr("Switch to grid view of the data")) + CFG_KEY_ENTRY(SHOW_FORM_VIEW, Qt::CTRL + Qt::Key_Period, QObject::tr("Switch to form view of the data")) +) + +class GUI_API_EXPORT DataView : public QTabWidget, public ExtActionContainer +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + SHOW_GRID_VIEW, + SHOW_FORM_VIEW, + TABS_ON_TOP, + TABS_AT_BOTTOM, + // Grid view + REFRESH_DATA, + FIRST_PAGE, + PREV_PAGE, + NEXT_PAGE, + LAST_PAGE, + PAGE_EDIT, + FILTER_VALUE, + FILTER, + FILTER_STRING, + FILTER_SQL, + FILTER_REGEXP, + GRID_TOTAL_ROWS, + SELECTIVE_COMMIT, + SELECTIVE_ROLLBACK, + // Form view + FORM_TOTAL_ROWS, + FORM_CURRENT_ROW + }; + + enum class ActionGroup + { + FILTER_MODE, + TABS_POSITION + }; + + enum ToolBar + { + TOOLBAR_GRID, + TOOLBAR_FORM + }; + + explicit DataView(QWidget *parent = 0); + + void init(SqlQueryModel* model); + + FormView* getFormView() const; + SqlQueryView* getGridView() const; + SqlQueryModel* getModel() const; + QToolBar* getToolBar(int toolbar) const; + bool isUncommited() const; + + static void staticInit(); + static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_GRID); + static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_GRID); + static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_GRID); + static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_GRID); + + protected: + void createActions(); + void setupDefShortcuts(); + + private: + enum class TabsPosition + { + TOP, + BOTTOM + }; + + enum class IndexModifier + { + FIRST, + PREV, + NEXT, + LAST + }; + + enum class FilterMode + { + STRING, + SQL, + REGEXP + }; + + static void createStaticActions(); + static void loadTabsMode(); + + void initFormView(); + void initFilter(); + void initUpdates(); + void initSlots(); + void initPageEdit(); + void createContents(); + void goToFormRow(IndexModifier idxMod); + void setNavigationState(bool enabled); + void updateNavigationState(); + void updateGridNavigationState(); + void goToPage(const QString& pageStr); + void updatePageEdit(); + void updateResultsCount(int resultsCount); + void updateCurrentFormViewRow(); + void setFormViewEnabled(bool enabled); + void readData(); + void updateFilterIcon(); + void initFormViewForNewRow(); + void formViewFocusFirstEditor(); + + static FilterMode filterMode; + static TabsPosition tabsPosition; + static QHash staticActions; + static QHash staticActionGroups; + + QToolBar* gridToolBar = nullptr; + QToolBar* formToolBar = nullptr; + SqlQueryView* gridView = nullptr; + SqlQueryModel* model = nullptr; + FormView* formView = nullptr; + QWidget* gridWidget = nullptr; + QWidget* formWidget = nullptr; + ExtLineEdit* filterEdit = nullptr; + QLabel* rowCountLabel = nullptr; + QLabel* formViewRowCountLabel = nullptr; + QLabel* formViewCurrentRowLabel = nullptr; + ExtLineEdit* pageEdit = nullptr; + IntValidator* pageValidator = nullptr; + bool navigationState = false; + bool totalPagesAvailable = false; + QMutex manualPageChangeMutex; + bool uncommittedGrid = false; + bool uncommittedForm = false; + + signals: + + public slots: + void refreshData(); + + private slots: + void dataLoadingEnded(bool successful); + void executionSuccessful(); + void totalRowsAndPagesAvailable(); + void insertRow(); + void insertMultipleRows(); + void deleteRow(); + void commitGrid(); + void rollbackGrid(); + void selectiveCommitGrid(); + void selectiveRollbackGrid(); + void firstPage(); + void prevPage(); + void nextPage(); + void lastPage(); + void pageEntered(); + void applyFilter(); + void resetFilter(); + void commitForm(); + void rollbackForm(); + void firstRow(); + void prevRow(); + void nextRow(); + void lastRow(); + void columnsHeaderClicked(int columnIdx); + void tabChanged(int newIndex); + void updateFormNavigationState(); + void updateFormCommitRollbackActions(); + void updateCommitRollbackActions(bool enabled); + void updateSelectiveCommitRollbackActions(bool enabled); + void showGridView(); + void showFormView(); + void updateTabsMode(); + void filterModeSelected(); +}; + +int qHash(DataView::ActionGroup action); + +#endif // DATAVIEW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp new file mode 100644 index 0000000..baa0f76 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dblistmodel.cpp @@ -0,0 +1,184 @@ +#include "dblistmodel.h" +#include "services/dbmanager.h" +#include "dbtree/dbtree.h" +#include "dbtree/dbtreemodel.h" +#include "uiconfig.h" +#include + +DbListModel::DbListModel(QObject *parent) : + QAbstractListModel(parent) +{ + unsortedList = DBLIST->getConnectedDbList(); + + connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*))); + connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*))); + + setSortMode(CFG_UI.General.SqlEditorDbListOrder.get()); +} + +DbListModel::~DbListModel() +{ +} + +QVariant DbListModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= dbList.size()) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole) + return dbList[index.row()]->getName(); + + return QVariant(); +} + +int DbListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return dbList.count(); +} + +QModelIndex DbListModel::sibling(int row, int column, const QModelIndex &idx) const +{ + if (!idx.isValid() || column != 0 || row >= dbList.count()) + return QModelIndex(); + + return createIndex(row, 0); +} + +Db* DbListModel::getDb(int index) +{ + if (index < 0 || index >= dbList.size()) + return nullptr; + + return dbList[index]; +} + +void DbListModel::setSortMode(DbListModel::SortMode sortMode) +{ + this->sortMode = sortMode; + sort(); +} + +DbListModel::SortMode DbListModel::getSortMode() const +{ + return sortMode; +} + +void DbListModel::setSortMode(const QString& sortMode) +{ + if (sortMode == "LikeDbTree") + this->sortMode = SortMode::LikeDbTree; + else if (sortMode == "Alphabetical") + this->sortMode = SortMode::Alphabetical; + else + this->sortMode = SortMode::ConnectionOrder; + + sort(); +} + +QString DbListModel::getSortModeString() const +{ + switch (sortMode) + { + case DbListModel::SortMode::LikeDbTree: + return "LikeDbTree"; + case DbListModel::SortMode::Alphabetical: + return "Alphabetical"; + case DbListModel::SortMode::ConnectionOrder: + break; + } + return "ConnectionOrder"; +} + +void DbListModel::setCombo(QComboBox* combo) +{ + comboBox = combo; +} + +void DbListModel::sort() +{ + dbList = unsortedList; + switch (sortMode) + { + case DbListModel::SortMode::LikeDbTree: + { + DbTreeComparer comparer; + qSort(dbList.begin(), dbList.end(), comparer); + break; + } + case DbListModel::SortMode::Alphabetical: + { + AlphaComparer comparer; + qSort(dbList.begin(), dbList.end(), comparer); + break; + } + case DbListModel::SortMode::ConnectionOrder: + break; + } +} + +void DbListModel::dbConnected(Db* db) +{ + QString current; + if (comboBox) + current = comboBox->currentText(); + + beginResetModel(); + unsortedList += db; + sort(); + endResetModel(); + + if (!current.isNull()) + comboBox->setCurrentText(current); + else + comboBox->setCurrentText(dbList.first()->getName()); +} + +void DbListModel::dbDisconnected(Db* db) +{ + QString current; + int newIdx = -1; + if (comboBox) + { + if (db->getName() == comboBox->currentText()) + newIdx = 0; + else + current = comboBox->currentText(); + } + + beginResetModel(); + dbList.removeAll(db); + unsortedList.removeAll(db); + endResetModel(); + + if (!current.isNull()) + comboBox->setCurrentText(current); + else if (newIdx > -1) + comboBox->setCurrentIndex(newIdx); +} + +DbListModel::DbTreeComparer::DbTreeComparer() +{ + // TODO when sorting or D&D databases in the tree, this should be updated + QList allItems = DBTREE->getModel()->getAllItemsAsFlatList(); + dbTreeOrder.clear(); + foreach (DbTreeItem* item, allItems) + { + if (item->getType() != DbTreeItem::Type::DB) + continue; + + dbTreeOrder << item->text(); + } +} + +bool DbListModel::DbTreeComparer::operator()(Db* db1, Db* db2) +{ + return dbTreeOrder.indexOf(db1->getName()) < dbTreeOrder.indexOf(db2->getName()); +} + +bool DbListModel::AlphaComparer::operator()(Db* db1, Db* db2) +{ + return db1->getName().compare(db2->getName()) < 0; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dblistmodel.h b/SQLiteStudio3/guiSQLiteStudio/dblistmodel.h new file mode 100644 index 0000000..121db4d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dblistmodel.h @@ -0,0 +1,66 @@ +#ifndef DBLISTMODEL_H +#define DBLISTMODEL_H + +#include "db/db.h" +#include "guiSQLiteStudio_global.h" +#include + +class QComboBox; + +class GUI_API_EXPORT DbListModel : public QAbstractListModel +{ + Q_OBJECT + public: + enum class SortMode + { + LikeDbTree, + Alphabetical, + ConnectionOrder + }; + + explicit DbListModel(QObject *parent = 0); + ~DbListModel(); + + QVariant data(const QModelIndex & index, int role) const; + int rowCount(const QModelIndex & parent = QModelIndex()) const; + QModelIndex sibling(int row, int column, const QModelIndex & idx) const; + + Db* getDb(int index); + void setSortMode(SortMode sortMode); + SortMode getSortMode() const; + void setSortMode(const QString& sortMode); + QString getSortModeString() const; + void setCombo(QComboBox* combo); + + private: + using QAbstractItemModel::sort; + + class DbTreeComparer + { + public: + DbTreeComparer(); + bool operator()(Db* db1, Db* db2); + + private: + QStringList dbTreeOrder; + }; + + class AlphaComparer + { + public: + bool operator()(Db* db1, Db* db2); + }; + + void sort(); + + QList unsortedList; + QList dbList; + SortMode sortMode = SortMode::ConnectionOrder; + QComboBox* comboBox = nullptr; + + private slots: + void dbConnected(Db* db); + void dbDisconnected(Db* db); +}; + +#endif // DBLISTMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp b/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp new file mode 100644 index 0000000..25686b5 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.cpp @@ -0,0 +1,309 @@ +#include "dbobjectdialogs.h" +#include "mainwindow.h" +#include "dialogs/indexdialog.h" +#include "dialogs/triggerdialog.h" +#include "common/utils_sql.h" +#include "dbtree/dbtree.h" +#include "services/notifymanager.h" +#include "mdiarea.h" +#include "mdiwindow.h" +#include "windows/tablewindow.h" +#include "windows/viewwindow.h" +#include "db/sqlquery.h" +#include "services/config.h" +#include +#include + +DbObjectDialogs::DbObjectDialogs(Db* db) : + db(db) +{ + mainWindow = MainWindow::getInstance(); + mdiArea = mainWindow->getMdiArea(); + parentWidget = mainWindow; +} + +DbObjectDialogs::DbObjectDialogs(Db* db, QWidget* parentWidget) : + db(db), parentWidget(parentWidget) +{ + mainWindow = MainWindow::getInstance(); + mdiArea = mainWindow->getMdiArea(); +} + +void DbObjectDialogs::addIndex(const QString& table) +{ + IndexDialog dialog(db, parentWidget); + if (!table.isNull()) + dialog.setTable(table); + + dialog.exec(); +} + +void DbObjectDialogs::editIndex(const QString& index) +{ + if (index.isNull()) + { + qWarning() << "Tried to edit null index."; + return; + } + + IndexDialog dialog(db, index, parentWidget); + dialog.exec(); +} + +void DbObjectDialogs::addTriggerOnTable(const QString& table) +{ + addTrigger(table, QString::null); +} + +void DbObjectDialogs::addTriggerOnView(const QString& view) +{ + addTrigger(QString::null, view); +} + +void DbObjectDialogs::addTrigger(const QString& table, const QString& view) +{ + TriggerDialog dialog(db, parentWidget); + if (!table.isNull()) + dialog.setParentTable(table); + else if (!view.isNull()) + dialog.setParentView(view); + else + return; + + dialog.exec(); +} + +void DbObjectDialogs::editTrigger(const QString& trigger) +{ + if (trigger.isNull()) + { + qWarning() << "Tried to edit null trigger."; + return; + } + + TriggerDialog dialog(db, parentWidget); + dialog.setTrigger(trigger); + dialog.exec(); +} + +ViewWindow* DbObjectDialogs::addView(const QString &initialSelect) +{ + ViewWindow* win = new ViewWindow(db, mdiArea); + win->setSelect(initialSelect); + mdiArea->addSubWindow(win); + return win; +} + +ViewWindow* DbObjectDialogs::editView(const QString& database, const QString& view) +{ + ViewWindow* win = nullptr; + foreach (MdiWindow* mdiWin, mdiArea->getWindows()) + { + win = dynamic_cast(mdiWin->getMdiChild()); + if (!win) + continue; + + if (win->getDb() == db && win->getView() == view) + { + mdiArea->setActiveSubWindow(mdiWin); + return win; + } + } + + win = new ViewWindow(mdiArea, db, database, view); + if (win->isInvalid()) + { + delete win; + return nullptr; + } + + mdiArea->addSubWindow(win); + return win; +} + +void DbObjectDialogs::editObject(const QString& name) +{ + editObject("main", name); +} + +void DbObjectDialogs::editObject(const QString& database, const QString& name) +{ + Type type = getObjectType(database, name); + switch (type) + { + case Type::TABLE: + editTable(database, name); + break; + case Type::INDEX: + editIndex(name); + break; + case Type::TRIGGER: + editTrigger(name); + break; + case Type::VIEW: + editView(database, name); + break; + default: + { + qCritical() << "Unknown object type while trying to edit object. Object name:" << database << "." << name; + return; + } + } +} + +bool DbObjectDialogs::dropObject(const QString& name) +{ + return dropObject("main", name); +} + +bool DbObjectDialogs::dropObject(const QString& database, const QString& name) +{ + static const QString dropSql2 = "DROP %1 %2;"; + static const QString dropSql3 = "DROP %1 %2.%3;"; + + Dialect dialect = db->getDialect(); + QString dbName = wrapObjIfNeeded(database, dialect); + + Type type = getObjectType(database, name); + QString title; + QString message; + QString typeForSql; + switch (type) + { + case Type::TABLE: + title = tr("Delete table"); + message = tr("Are you sure you want to delete table %1?"); + typeForSql = "TABLE"; + break; + case Type::INDEX: + title = tr("Delete index"); + message = tr("Are you sure you want to delete index %1?"); + typeForSql = "INDEX"; + break; + case Type::TRIGGER: + title = tr("Delete trigger"); + message = tr("Are you sure you want to delete trigger %1?"); + typeForSql = "TRIGGER"; + break; + case Type::VIEW: + title = tr("Delete view"); + message = tr("Are you sure you want to delete view %1?"); + typeForSql = "VIEW"; + break; + default: + { + qCritical() << "Unknown object type while trying to drop object. Object name:" << database << "." << name; + return false; + } + } + + if (!noConfirmation) + { + QMessageBox::StandardButton resp = QMessageBox::question(parentWidget, title, message.arg(name)); + if (resp != QMessageBox::Yes) + return false; + } + + SqlQueryPtr results; + + QString finalSql; + if (dialect == Dialect::Sqlite3) + finalSql = dropSql3.arg(typeForSql, dbName, wrapObjIfNeeded(name, dialect)); + else + finalSql = dropSql2.arg(typeForSql, wrapObjIfNeeded(name, dialect)); + + results = db->exec(finalSql); + if (results->isError()) + { + notifyError(tr("Error while dropping %1: %2").arg(name).arg(results->getErrorText())); + qCritical() << "Error while dropping object " << database << "." << name << ":" << results->getErrorText(); + return false; + } + + CFG->addDdlHistory(finalSql, db->getName(), db->getPath()); + if (!noSchemaRefreshing) + DBTREE->refreshSchema(db); + + return true; +} + +DbObjectDialogs::Type DbObjectDialogs::getObjectType(const QString& database, const QString& name) +{ + static const QString typeSql = "SELECT type FROM %1.sqlite_master WHERE name = ?;"; + static const QStringList types = {"table", "index", "trigger", "view"}; + + Dialect dialect = db->getDialect(); + QString dbName = wrapObjIfNeeded(database, dialect); + SqlQueryPtr results = db->exec(typeSql.arg(dbName), {name}); + if (results->isError()) + { + qCritical() << "Could not get object type. Object name:" << database << "." << name << ", error:" + << results->getErrorText(); + return Type::UNKNOWN; + } + + QString typeStr = results->getSingleCell().toString(); + return static_cast(types.indexOf(typeStr)); +} +bool DbObjectDialogs::getNoSchemaRefreshing() const +{ + return noSchemaRefreshing; +} + +void DbObjectDialogs::setNoSchemaRefreshing(bool value) +{ + noSchemaRefreshing = value; +} + +bool DbObjectDialogs::getNoConfirmation() const +{ + return noConfirmation; +} + +void DbObjectDialogs::setNoConfirmation(bool value) +{ + noConfirmation = value; +} + + +TableWindow* DbObjectDialogs::editTable(const QString& database, const QString& table) +{ + TableWindow* win = nullptr; + foreach (MdiWindow* mdiWin, mdiArea->getWindows()) + { + win = dynamic_cast(mdiWin->getMdiChild()); + if (!win) + continue; + + if (win->getDb() == db && win->getTable() == table) + { + mdiArea->setActiveSubWindow(mdiWin); + return win; + } + } + + win = new TableWindow(mdiArea, db, database, table); + if (win->isInvalid()) + { + delete win; + return nullptr; + } + + mdiArea->addSubWindow(win); + return win; +} + +TableWindow *DbObjectDialogs::addTableSimilarTo(const QString &database, const QString &table) +{ + TableWindow* win = new TableWindow(mdiArea, db, database, table); + mdiArea->addSubWindow(win); + win->useCurrentTableAsBaseForNew(); + return win; +} + +TableWindow* DbObjectDialogs::addTable() +{ + TableWindow* win = new TableWindow(db, mdiArea); + mdiArea->addSubWindow(win); + return win; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h b/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h new file mode 100644 index 0000000..bedbab8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbobjectdialogs.h @@ -0,0 +1,67 @@ +#ifndef DBOBJECTDIALOGS_H +#define DBOBJECTDIALOGS_H + +#include "db/db.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class QWidget; +class MainWindow; +class MdiArea; +class TableWindow; +class ViewWindow; + +class GUI_API_EXPORT DbObjectDialogs : public QObject +{ + public: + explicit DbObjectDialogs(Db* db); + DbObjectDialogs(Db* db, QWidget* parentWidget); + + TableWindow* addTable(); + TableWindow* editTable(const QString& database, const QString& table); + TableWindow* addTableSimilarTo(const QString& database, const QString& table); + + void addIndex(const QString& table); + void editIndex(const QString& index); + + void addTriggerOnTable(const QString& table); + void addTriggerOnView(const QString& view); + void addTrigger(const QString& table, const QString& view); + void editTrigger(const QString& trigger); + + ViewWindow* addView(const QString& initialSelect = QString()); + ViewWindow* editView(const QString& database, const QString& view); + + void editObject(const QString& name); + void editObject(const QString& database, const QString& name); + bool dropObject(const QString& name); + bool dropObject(const QString& database, const QString& name); + + bool getNoConfirmation() const; + void setNoConfirmation(bool value); + + bool getNoSchemaRefreshing() const; + void setNoSchemaRefreshing(bool value); + + private: + enum class Type + { + TABLE = 0, + INDEX = 1, + TRIGGER = 2, + VIEW = 3, + UNKNOWN = -1 + }; + + Type getObjectType(const QString& database, const QString& name); + + Db* db = nullptr; + QWidget* parentWidget = nullptr; + MainWindow* mainWindow = nullptr; + MdiArea* mdiArea = nullptr; + bool noConfirmation = false; + bool noSchemaRefreshing = false; +}; + +#endif // DBOBJECTDIALOGS_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp new file mode 100644 index 0000000..99d3f27 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.cpp @@ -0,0 +1,120 @@ +#include "dbobjlistmodel.h" +#include "db/db.h" +#include +#include + +DbObjListModel::DbObjListModel(QObject *parent) : + QAbstractListModel(parent) +{ +} + +QVariant DbObjListModel::data(const QModelIndex& index, int role) const +{ + if (index.row() < 0 || index.row() >= objectList.size()) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + if (sortMode == SortMode::Alphabetical) + return objectList[index.row()]; + else + return unsortedObjectList[index.row()]; + } + + return QVariant(); +} + +int DbObjListModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + + return objectList.count(); +} + +QModelIndex DbObjListModel::sibling(int row, int column, const QModelIndex& idx) const +{ + if (!idx.isValid() || column != 0 || row >= objectList.count()) + return QModelIndex(); + + return createIndex(row, 0); +} + +Db* DbObjListModel::getDb() const +{ + return db; +} + +void DbObjListModel::setDb(Db* value) +{ + db = value; + updateList(); +} + +DbObjListModel::SortMode DbObjListModel::getSortMode() const +{ + return sortMode; +} + +void DbObjListModel::setSortMode(const SortMode& value) +{ + sortMode = value; + beginResetModel(); + endResetModel(); +} + +DbObjListModel::ObjectType DbObjListModel::getType() const +{ + return type; +} + +void DbObjListModel::setType(const ObjectType& value) +{ + type = value; + updateList(); +} + +void DbObjListModel::updateList() +{ + if (!db || type == ObjectType::null) + return; + + beginResetModel(); + SchemaResolver resolver(db); + resolver.setIgnoreSystemObjects(!includeSystemObjects); + objectList = resolver.getObjects(typeString().toLower()); + unsortedObjectList = objectList; + qSort(objectList); + endResetModel(); +} + +QString DbObjListModel::typeString() const +{ + switch (type) + { + case ObjectType::TABLE: + return "TABLE"; + case ObjectType::INDEX: + return "INDEX"; + case ObjectType::TRIGGER: + return "TRIGGER"; + case ObjectType::VIEW: + return "VIEW"; + case ObjectType::null: + break; + } + return QString::null; +} +bool DbObjListModel::getIncludeSystemObjects() const +{ + return includeSystemObjects; +} + +void DbObjListModel::setIncludeSystemObjects(bool value) +{ + includeSystemObjects = value; +} + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h b/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h new file mode 100644 index 0000000..cac1202 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbobjlistmodel.h @@ -0,0 +1,59 @@ +#ifndef DBOBJLISTMODEL_H +#define DBOBJLISTMODEL_H + +#include "guiSQLiteStudio_global.h" +#include + +class Db; + +class GUI_API_EXPORT DbObjListModel : public QAbstractListModel +{ + Q_OBJECT + + public: + enum class SortMode + { + LikeInDb, + Alphabetical + }; + + enum class ObjectType + { + TABLE, + INDEX, + TRIGGER, + VIEW, + null + }; + + explicit DbObjListModel(QObject *parent = 0); + + QVariant data(const QModelIndex & index, int role) const; + int rowCount(const QModelIndex & parent = QModelIndex()) const; + QModelIndex sibling(int row, int column, const QModelIndex & idx) const; + + Db* getDb() const; + void setDb(Db* value); + + SortMode getSortMode() const; + void setSortMode(const SortMode& value); + + ObjectType getType() const; + void setType(const ObjectType& value); + + bool getIncludeSystemObjects() const; + void setIncludeSystemObjects(bool value); + + private: + void updateList(); + QString typeString() const; + + ObjectType type = ObjectType::null; + Db* db = nullptr; + SortMode sortMode = SortMode::LikeInDb; + QStringList objectList; + QStringList unsortedObjectList; + bool includeSystemObjects = true; +}; + +#endif // DBOBJLISTMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp new file mode 100644 index 0000000..98baaa9 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp @@ -0,0 +1,1557 @@ +#include "dbtree.h" +#include "dbtreeitem.h" +#include "ui_dbtree.h" +#include "actionentry.h" +#include "common/utils_sql.h" +#include "dbtreemodel.h" +#include "dialogs/dbdialog.h" +#include "services/dbmanager.h" +#include "iconmanager.h" +#include "common/global.h" +#include "services/notifymanager.h" +#include "mainwindow.h" +#include "mdiarea.h" +#include "common/unused.h" +#include "dbobjectdialogs.h" +#include "common/userinputfilter.h" +#include "common/widgetcover.h" +#include "windows/tablewindow.h" +#include "dialogs/indexdialog.h" +#include "dialogs/triggerdialog.h" +#include "dialogs/exportdialog.h" +#include "dialogs/importdialog.h" +#include "dialogs/populatedialog.h" +#include "services/importmanager.h" +#include "windows/editorwindow.h" +#include "uiconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(DbTree) +QHash> DbTree::allowedTypesInside; +QSet DbTree::draggableTypes; + +DbTree::DbTree(QWidget *parent) : + QDockWidget(parent), + ui(new Ui::DbTree) +{ + init(); +} + +DbTree::~DbTree() +{ + delete ui; + delete treeModel; +} + +void DbTree::staticInit() +{ + initDndTypes(); +} + +void DbTree::init() +{ + ui->setupUi(this); + initDndTypes(); + + ui->nameFilter->setClearButtonEnabled(true); + + widgetCover = new WidgetCover(this); + widgetCover->initWithInterruptContainer(); + widgetCover->hide(); + connect(widgetCover, SIGNAL(cancelClicked()), this, SLOT(interrupt())); + + treeModel = new DbTreeModel(); + treeModel->setTreeView(ui->treeView); + + new UserInputFilter(ui->nameFilter, treeModel, SLOT(applyFilter(QString))); + + ui->treeView->setDbTree(this); + ui->treeView->setModel(treeModel); + + initActions(); + + if (DBLIST->getDbList().size() > 0) + treeModel->loadDbList(); + + connect(DBLIST, SIGNAL(dbListLoaded()), treeModel, SLOT(loadDbList())); + connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &DbTree::currentChanged); + connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*))); + connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*))); + connect(IMPORT_MANAGER, SIGNAL(schemaModified(Db*)), this, SLOT(refreshSchema(Db*))); + + connect(CFG_UI.Fonts.DbTree, SIGNAL(changed(QVariant)), this, SLOT(refreshFont())); + + updateActionsForCurrent(); +} + +void DbTree::createActions() +{ + createAction(COPY, ICONS.ACT_COPY, tr("Copy"), this, SLOT(copy()), this); + createAction(PASTE, ICONS.ACT_PASTE, tr("Paste"), this, SLOT(paste()), this); + createAction(SELECT_ALL, ICONS.ACT_SELECT_ALL, tr("Select all"), this, SLOT(selectAll()), this); + createAction(CREATE_GROUP, ICONS.DIRECTORY_ADD, tr("Create a group"), this, SLOT(createGroup()), this); + createAction(DELETE_GROUP, ICONS.DIRECTORY_DEL, tr("Delete the group"), this, SLOT(deleteGroup()), this); + createAction(RENAME_GROUP, ICONS.DIRECTORY_EDIT, tr("Rename the group"), this, SLOT(renameGroup()), this); + createAction(ADD_DB, ICONS.DATABASE_ADD, tr("Add a database"), this, SLOT(addDb()), this); + createAction(EDIT_DB, ICONS.DATABASE_EDIT, tr("Edit the database"), this, SLOT(editDb()), this); + createAction(DELETE_DB, ICONS.DATABASE_DEL, tr("Remove the database"), this, SLOT(removeDb()), this); + createAction(CONNECT_TO_DB, ICONS.DATABASE_CONNECT, tr("Connect to the database"), this, SLOT(connectToDb()), this); + createAction(DISCONNECT_FROM_DB, ICONS.DATABASE_DISCONNECT, tr("Disconnect from the database"), this, SLOT(disconnectFromDb()), this); + createAction(IMPORT_INTO_DB, ICONS.IMPORT, tr("Import"), this, SLOT(import()), this); + createAction(EXPORT_DB, ICONS.DATABASE_EXPORT, tr("Export the database"), this, SLOT(exportDb()), this); + createAction(CONVERT_DB, ICONS.CONVERT_DB, tr("Convert database type"), this, SLOT(convertDb()), this); + createAction(VACUUM_DB, ICONS.VACUUM_DB, tr("Vacuum"), this, SLOT(vacuumDb()), this); + createAction(INTEGRITY_CHECK, ICONS.INTEGRITY_CHECK, tr("Integrity check"), this, SLOT(integrityCheck()), this); + createAction(ADD_TABLE, ICONS.TABLE_ADD, tr("Create a table"), this, SLOT(addTable()), this); + createAction(EDIT_TABLE, ICONS.TABLE_EDIT, tr("Edit the table"), this, SLOT(editTable()), this); + createAction(DEL_TABLE, ICONS.TABLE_DEL, tr("Drop the table"), this, SLOT(delTable()), this); + createAction(EXPORT_TABLE, ICONS.TABLE_EXPORT, tr("Export the table"), this, SLOT(exportTable()), this); + createAction(IMPORT_TABLE, ICONS.TABLE_IMPORT, tr("Import into the table"), this, SLOT(importTable()), this); + createAction(POPULATE_TABLE, ICONS.TABLE_POPULATE, tr("Populate table"), this, SLOT(populateTable()), this); + createAction(CREATE_SIMILAR_TABLE, ICONS.TABLE_CREATE_SIMILAR, tr("Create similar table"), this, SLOT(createSimilarTable()), this); + createAction(ADD_INDEX, ICONS.INDEX_ADD, tr("Create an index"), this, SLOT(addIndex()), this); + createAction(EDIT_INDEX, ICONS.INDEX_EDIT, tr("Edit the index"), this, SLOT(editIndex()), this); + createAction(DEL_INDEX, ICONS.INDEX_DEL, tr("Drop the index"), this, SLOT(delIndex()), this); + createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create a trigger"), this, SLOT(addTrigger()), this); + createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit the trigger"), this, SLOT(editTrigger()), this); + createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Drop the trigger"), this, SLOT(delTrigger()), this); + createAction(ADD_VIEW, ICONS.VIEW_ADD, tr("Create a view"), this, SLOT(addView()), this); + createAction(EDIT_VIEW, ICONS.VIEW_EDIT, tr("Edit the view"), this, SLOT(editView()), this); + createAction(DEL_VIEW, ICONS.VIEW_DEL, tr("Drop the view"), this, SLOT(delView()), this); + createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add a column"), this, SLOT(addColumn()), this); + createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit the column"), this, SLOT(editColumn()), this); + createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete the column"), this, SLOT(delColumn()), this); + createAction(DEL_SELECTED, ICONS.DELETE_SELECTED, tr("Delete selected items"), this, SLOT(deleteSelected()), this); + createAction(CLEAR_FILTER, tr("Clear filter"), ui->nameFilter, SLOT(clear()), this); + createAction(REFRESH_SCHEMAS, ICONS.DATABASE_RELOAD, tr("Refresh all database schemas"), this, SLOT(refreshSchemas()), this); + createAction(REFRESH_SCHEMA, ICONS.DATABASE_RELOAD, tr("Refresh selected database schema"), this, SLOT(refreshSchema()), this); +} + +void DbTree::updateActionStates(const QStandardItem *item) +{ + QList enabled; + const DbTreeItem* dbTreeItem = dynamic_cast(item); + if (item != nullptr) + { + bool isDbOpen = false; + DbTreeItem* parentItem = dbTreeItem->parentDbTreeItem(); + DbTreeItem* grandParentItem = parentItem ? parentItem->parentDbTreeItem() : nullptr; + + // Add database should always be available, as well as a copy of an item + enabled << ADD_DB << COPY; + + if (isMimeDataValidForItem(QApplication::clipboard()->mimeData(), dbTreeItem)) + enabled << PASTE; + + enabled << CLEAR_FILTER; + + // Group actions + if (dbTreeItem->getType() == DbTreeItem::Type::DIR) + enabled << CREATE_GROUP << RENAME_GROUP << DELETE_GROUP << ADD_DB; + + if (dbTreeItem->getDb()) + { + enabled << DELETE_DB << EDIT_DB; + if (dbTreeItem->getDb()->isOpen()) + { + enabled << DISCONNECT_FROM_DB << ADD_TABLE << ADD_VIEW << IMPORT_INTO_DB << EXPORT_DB << REFRESH_SCHEMA << CONVERT_DB + << VACUUM_DB << INTEGRITY_CHECK; + isDbOpen = true; + } + else + enabled << CONNECT_TO_DB; + } + + if (isDbOpen) + { + switch (dbTreeItem->getType()) + { + case DbTreeItem::Type::ITEM_PROTOTYPE: + break; + case DbTreeItem::Type::DIR: + // It's handled outside of "item with db", above + break; + case DbTreeItem::Type::DB: + enabled << CREATE_GROUP << DELETE_DB << EDIT_DB; + break; + case DbTreeItem::Type::TABLES: + break; + case DbTreeItem::Type::TABLE: + enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN << CREATE_SIMILAR_TABLE; + enabled << ADD_INDEX << ADD_TRIGGER; + break; + case DbTreeItem::Type::VIRTUAL_TABLE: + // TODO change below when virtual tables can be edited +// enabled << EDIT_TABLE << DEL_TABLE; + enabled << DEL_TABLE; + break; + case DbTreeItem::Type::INDEXES: + enabled << EDIT_TABLE << DEL_TABLE; + enabled << ADD_INDEX << ADD_TRIGGER; + break; + case DbTreeItem::Type::INDEX: + enabled << EDIT_TABLE << DEL_TABLE; + enabled << EDIT_INDEX << DEL_INDEX; + enabled << ADD_INDEX << ADD_TRIGGER; + break; + case DbTreeItem::Type::TRIGGERS: + { + if (parentItem->getType() == DbTreeItem::Type::TABLE) + { + enabled << EDIT_TABLE << DEL_TABLE; + enabled << ADD_INDEX << ADD_TRIGGER; + } + else + { + enabled << EDIT_VIEW << DEL_VIEW; + enabled << ADD_TRIGGER; + } + + enabled << ADD_TRIGGER; + break; + } + case DbTreeItem::Type::TRIGGER: + { + if (grandParentItem->getType() == DbTreeItem::Type::TABLE) + { + enabled << EDIT_TABLE << DEL_TABLE; + enabled << ADD_INDEX << ADD_TRIGGER; + } + else + { + enabled << EDIT_VIEW << DEL_VIEW; + enabled << ADD_TRIGGER; + } + + enabled << EDIT_TRIGGER << DEL_TRIGGER; + break; + } + case DbTreeItem::Type::VIEWS: + break; + case DbTreeItem::Type::VIEW: + enabled << EDIT_VIEW << DEL_VIEW; + enabled << ADD_TRIGGER; + break; + case DbTreeItem::Type::COLUMNS: + enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN; + enabled << ADD_INDEX << ADD_TRIGGER; + break; + case DbTreeItem::Type::COLUMN: + enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN << DEL_COLUMN; + enabled << EDIT_COLUMN; + enabled << ADD_INDEX << ADD_TRIGGER; + break; + } + } + + // Do we have any deletable object selected? If yes, enable "Del" action. + bool enableDel = false; + for (DbTreeItem* selItem : getModel()->getItemsForIndexes(getView()->getSelectedIndexes())) + { + switch (selItem->getType()) + { + case DbTreeItem::Type::COLUMN: + case DbTreeItem::Type::DB: + case DbTreeItem::Type::DIR: + case DbTreeItem::Type::INDEX: + case DbTreeItem::Type::TABLE: + case DbTreeItem::Type::TRIGGER: + case DbTreeItem::Type::VIEW: + case DbTreeItem::Type::VIRTUAL_TABLE: + enableDel = true; + break; + case DbTreeItem::Type::COLUMNS: + case DbTreeItem::Type::INDEXES: + case DbTreeItem::Type::ITEM_PROTOTYPE: + case DbTreeItem::Type::TABLES: + case DbTreeItem::Type::TRIGGERS: + case DbTreeItem::Type::VIEWS: + break; + } + + if (enableDel) + { + enabled << DEL_SELECTED; + break; + } + } + } + else + { + enabled << CREATE_GROUP << ADD_DB; + } + + if (treeModel->rowCount() > 0) + enabled << SELECT_ALL; // if there's at least 1 item, enable this + + enabled << REFRESH_SCHEMAS; + + foreach (int action, actionMap.keys()) + setActionEnabled(action, enabled.contains(action)); +} + +void DbTree::setupActionsForMenu(DbTreeItem* currItem, QMenu* contextMenu) +{ + QList actions; + + ActionEntry dbEntry(ICONS.DATABASE, tr("Datatabase")); + dbEntry += ADD_DB; + dbEntry += EDIT_DB; + dbEntry += DELETE_DB; + + ActionEntry dbEntryExt(ICONS.DATABASE, tr("Datatabase")); + dbEntryExt += CONNECT_TO_DB; + dbEntryExt += DISCONNECT_FROM_DB; + dbEntryExt += _separator; + dbEntryExt += REFRESH_SCHEMA; + dbEntryExt += _separator; + dbEntryExt += ADD_DB; + dbEntryExt += EDIT_DB; + dbEntryExt += DELETE_DB; + + ActionEntry groupEntry(ICONS.DIRECTORY, tr("Grouping")); + groupEntry += CREATE_GROUP; + groupEntry += RENAME_GROUP; + groupEntry += DELETE_GROUP; + + if (currItem) + { + DbTreeItem* parentItem = currItem->parentDbTreeItem(); + DbTreeItem* grandParentItem = parentItem ? parentItem->parentDbTreeItem() : nullptr; + DbTreeItem::Type itemType = currItem->getType(); + switch (itemType) + { + case DbTreeItem::Type::DIR: + { + actions += ActionEntry(CREATE_GROUP); + actions += ActionEntry(RENAME_GROUP); + actions += ActionEntry(DELETE_GROUP); + actions += ActionEntry(_separator); + actions += dbEntry; + break; + } + case DbTreeItem::Type::DB: + { + if (currItem->getDb()->isValid()) + { + actions += ActionEntry(CONNECT_TO_DB); + actions += ActionEntry(DISCONNECT_FROM_DB); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_DB); + actions += ActionEntry(EDIT_DB); + actions += ActionEntry(DELETE_DB); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(ADD_INDEX); + actions += ActionEntry(ADD_TRIGGER); + actions += ActionEntry(ADD_VIEW); + actions += ActionEntry(_separator); + actions += ActionEntry(REFRESH_SCHEMA); + actions += ActionEntry(IMPORT_INTO_DB); + actions += ActionEntry(EXPORT_DB); + actions += ActionEntry(CONVERT_DB); + actions += ActionEntry(VACUUM_DB); + actions += ActionEntry(INTEGRITY_CHECK); + actions += ActionEntry(_separator); + } + else + { + actions += ActionEntry(ADD_DB); + actions += ActionEntry(EDIT_DB); + actions += ActionEntry(DELETE_DB); + actions += ActionEntry(_separator); + } + break; + } + case DbTreeItem::Type::TABLES: + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::TABLE: + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(EDIT_TABLE); + actions += ActionEntry(DEL_TABLE); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_COLUMN); + actions += ActionEntry(ADD_INDEX); + actions += ActionEntry(ADD_TRIGGER); + actions += ActionEntry(_separator); + actions += ActionEntry(IMPORT_TABLE); + actions += ActionEntry(EXPORT_TABLE); + actions += ActionEntry(POPULATE_TABLE); + actions += ActionEntry(CREATE_SIMILAR_TABLE); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::VIRTUAL_TABLE: + actions += ActionEntry(ADD_TABLE); + //actions += ActionEntry(EDIT_TABLE); // TODO uncomment when virtual tables have their own edition window + actions += ActionEntry(DEL_TABLE); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::INDEXES: + actions += ActionEntry(ADD_INDEX); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(EDIT_TABLE); + actions += ActionEntry(DEL_TABLE); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::INDEX: + actions += ActionEntry(ADD_INDEX); + actions += ActionEntry(EDIT_INDEX); + actions += ActionEntry(DEL_INDEX); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(EDIT_TABLE); + actions += ActionEntry(DEL_TABLE); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::TRIGGERS: + { + actions += ActionEntry(ADD_TRIGGER); + actions += ActionEntry(_separator); + if (parentItem->getType() == DbTreeItem::Type::TABLE) + { + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(EDIT_TABLE); + actions += ActionEntry(DEL_TABLE); + } + else + { + actions += ActionEntry(ADD_VIEW); + actions += ActionEntry(EDIT_VIEW); + actions += ActionEntry(DEL_VIEW); + } + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + } + case DbTreeItem::Type::TRIGGER: + { + actions += ActionEntry(ADD_TRIGGER); + actions += ActionEntry(EDIT_TRIGGER); + actions += ActionEntry(DEL_TRIGGER); + actions += ActionEntry(_separator); + if (grandParentItem->getType() == DbTreeItem::Type::TABLE) + { + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(EDIT_TABLE); + actions += ActionEntry(DEL_TABLE); + } + else + { + actions += ActionEntry(ADD_VIEW); + actions += ActionEntry(EDIT_VIEW); + actions += ActionEntry(DEL_VIEW); + } + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + } + case DbTreeItem::Type::VIEWS: + actions += ActionEntry(ADD_VIEW); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::VIEW: + actions += ActionEntry(ADD_VIEW); + actions += ActionEntry(EDIT_VIEW); + actions += ActionEntry(DEL_VIEW); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_TRIGGER); + actions += ActionEntry(EDIT_TRIGGER); + actions += ActionEntry(DEL_TRIGGER); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::COLUMNS: + actions += ActionEntry(ADD_COLUMN); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(EDIT_TABLE); + actions += ActionEntry(DEL_TABLE); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_INDEX); + actions += ActionEntry(ADD_TRIGGER); + actions += ActionEntry(_separator); + actions += ActionEntry(IMPORT_TABLE); + actions += ActionEntry(EXPORT_TABLE); + actions += ActionEntry(POPULATE_TABLE); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::COLUMN: + actions += ActionEntry(ADD_COLUMN); + actions += ActionEntry(EDIT_COLUMN); + actions += ActionEntry(DEL_COLUMN); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_TABLE); + actions += ActionEntry(EDIT_TABLE); + actions += ActionEntry(DEL_TABLE); + actions += ActionEntry(_separator); + actions += ActionEntry(ADD_INDEX); + actions += ActionEntry(ADD_TRIGGER); + actions += ActionEntry(_separator); + actions += ActionEntry(IMPORT_TABLE); + actions += ActionEntry(EXPORT_TABLE); + actions += ActionEntry(POPULATE_TABLE); + actions += ActionEntry(_separator); + actions += dbEntryExt; + break; + case DbTreeItem::Type::ITEM_PROTOTYPE: + break; + } + + actions += ActionEntry(_separator); + + if (itemType == DbTreeItem::Type::DB) + actions += groupEntry; + } + else + { + actions += dbEntry; + actions += ActionEntry(_separator); + actions += groupEntry; + } + + actions += COPY; + actions += PASTE; + actions += _separator; + actions += DEL_SELECTED; + actions += _separator; + actions += SELECT_ALL; + actions += ActionEntry(REFRESH_SCHEMAS); + + QMenu* subMenu = nullptr; + foreach (ActionEntry actionEntry, actions) + { + switch (actionEntry.type) + { + case ActionEntry::Type::SINGLE: + { + if (actionEntry.action == DbTree::_separator) + { + contextMenu->addSeparator(); + break; + } + contextMenu->addAction(actionMap[actionEntry.action]); + break; + } + case ActionEntry::Type::SUB_MENU: + { + subMenu = contextMenu->addMenu(actionEntry.subMenuIcon, actionEntry.subMenuLabel); + foreach (Action action, actionEntry.actions) + { + if (action == DbTree::_separator) + { + subMenu->addSeparator(); + continue; + } + subMenu->addAction(actionMap[action]); + } + break; + } + } + } +} + +void DbTree::initDndTypes() +{ + draggableTypes << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW << DbTreeItem::Type::DIR << DbTreeItem::Type::DB; + + allowedTypesInside[DbTreeItem::Type::DIR] << DbTreeItem::Type::DB << DbTreeItem::Type::DIR; + allowedTypesInside[DbTreeItem::Type::DB] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW; + allowedTypesInside[DbTreeItem::Type::TABLES] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW; + allowedTypesInside[DbTreeItem::Type::TABLE] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW; + allowedTypesInside[DbTreeItem::Type::VIEWS] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW; + allowedTypesInside[DbTreeItem::Type::VIEW] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW; +} + +QVariant DbTree::saveSession() +{ + treeModel->storeGroups(); + return QVariant(); +} + +void DbTree::restoreSession(const QVariant& sessionValue) +{ + UNUSED(sessionValue); +} + +DbTreeModel* DbTree::getModel() const +{ + return treeModel; +} + +DbTreeView*DbTree::getView() const +{ + return ui->treeView; +} + +bool DbTree::isMimeDataValidForItem(const QMimeData* mimeData, const DbTreeItem* item) +{ + if (mimeData->formats().contains(DbTreeModel::MIMETYPE)) + return areDbTreeItemsValidForItem(getModel()->getDragItems(mimeData), item); + else if (mimeData->hasUrls()) + return areUrlsValidForItem(mimeData->urls(), item); + + return false; +} + +bool DbTree::isItemDraggable(const DbTreeItem* item) +{ + return item && draggableTypes.contains(item->getType()); +} + +bool DbTree::areDbTreeItemsValidForItem(QList srcItems, const DbTreeItem* dstItem) +{ + QSet srcDbs; + QList srcTypes; + DbTreeItem::Type dstType = DbTreeItem::Type::DIR; // the empty space is treated as group + if (dstItem) + dstType = dstItem->getType(); + + for (DbTreeItem* srcItem : srcItems) + { + if (srcItem) + srcTypes << srcItem->getType(); + else + srcTypes << DbTreeItem::Type::ITEM_PROTOTYPE; + + if (srcItem->getDb()) + srcDbs << srcItem->getDb(); + } + + for (DbTreeItem::Type srcType : srcTypes) + { + if (!allowedTypesInside[dstType].contains(srcType)) + return false; + + if (dstType == DbTreeItem::Type::DB && !dstItem->getDb()->isOpen()) + return false; + } + + if (dstItem && dstItem->getDb() && srcDbs.contains(dstItem->getDb())) + return false; + + return true; +} + +bool DbTree::areUrlsValidForItem(const QList& srcUrls, const DbTreeItem* dstItem) +{ + UNUSED(dstItem); + for (const QUrl& srcUrl : srcUrls) + { + if (!srcUrl.isLocalFile()) + return false; + } + return true; +} + +void DbTree::showWidgetCover() +{ + widgetCover->show(); +} + +void DbTree::hideWidgetCover() +{ + widgetCover->hide(); +} + +void DbTree::setSelectedItem(DbTreeItem *item) +{ + ui->treeView->setCurrentIndex(item->index()); + ui->treeView->selectionModel()->select(item->index(), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); +} + +QToolBar* DbTree::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return nullptr; +} + +void DbTree::setActionEnabled(int action, bool enabled) +{ + actionMap[action]->setEnabled(enabled); +} + +Db* DbTree::getSelectedDb() +{ + DbTreeItem* item = ui->treeView->currentItem(); + if (!item) + return nullptr; + + return item->getDb(); +} + +Db* DbTree::getSelectedOpenDb() +{ + Db* db = getSelectedDb(); + if (!db || !db->isOpen()) + return nullptr; + + return db; +} + +TableWindow* DbTree::openTable(DbTreeItem* item) +{ + QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree. + Db* db = item->getDb(); + return openTable(db, database, item->text()); +} + +TableWindow* DbTree::openTable(Db* db, const QString& database, const QString& table) +{ + DbObjectDialogs dialogs(db); + return dialogs.editTable(database, table); +} + +void DbTree::editIndex(DbTreeItem* item) +{ + //QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree. + Db* db = item->getDb(); + + DbObjectDialogs dialogs(db); + dialogs.editIndex(item->text()); +} + +ViewWindow* DbTree::openView(DbTreeItem* item) +{ + QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree. + Db* db = item->getDb(); + return openView(db, database, item->text()); +} + +ViewWindow* DbTree::openView(Db* db, const QString& database, const QString& view) +{ + DbObjectDialogs dialogs(db); + return dialogs.editView(database, view); +} + +TableWindow* DbTree::newTable(DbTreeItem* item) +{ + Db* db = item->getDb(); + + DbObjectDialogs dialogs(db); + return dialogs.addTable(); +} + +ViewWindow* DbTree::newView(DbTreeItem* item) +{ + Db* db = item->getDb(); + + DbObjectDialogs dialogs(db); + return dialogs.addView(); +} + +void DbTree::editTrigger(DbTreeItem* item) +{ + //QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree. + Db* db = item->getDb(); + + DbObjectDialogs dialogs(db); + dialogs.editTrigger(item->text()); +} + +void DbTree::delSelectedObject() +{ + Db* db = getSelectedOpenDb(); + if (!db) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + if (!item) + return; + + DbObjectDialogs dialogs(db); + dialogs.dropObject(item->text()); // TODO add database prefix when supported +} + +void DbTree::filterUndeletableItems(QList& items) +{ + QMutableListIterator it(items); + DbTreeItem::Type type; + while (it.hasNext()) + { + type = it.next()->getType(); + switch (type) + { + case DbTreeItem::Type::TABLES: + case DbTreeItem::Type::INDEXES: + case DbTreeItem::Type::TRIGGERS: + case DbTreeItem::Type::VIEWS: + case DbTreeItem::Type::COLUMNS: + case DbTreeItem::Type::ITEM_PROTOTYPE: + it.remove(); + break; + case DbTreeItem::Type::DIR: + case DbTreeItem::Type::DB: + case DbTreeItem::Type::TABLE: + case DbTreeItem::Type::VIRTUAL_TABLE: + case DbTreeItem::Type::INDEX: + case DbTreeItem::Type::TRIGGER: + case DbTreeItem::Type::VIEW: + case DbTreeItem::Type::COLUMN: + break; + } + } +} + +void DbTree::filterItemsWithParentInList(QList& items) +{ + QMutableListIterator it(items); + DbTreeItem* item = nullptr; + DbTreeItem* pathItem = nullptr; + while (it.hasNext()) + { + item = it.next(); + foreach (pathItem, item->getPathToRoot().mid(1)) + { + if (items.contains(pathItem) && pathItem->getType() != DbTreeItem::Type::DIR) + { + it.remove(); + break; + } + } + } +} + +void DbTree::deleteItem(DbTreeItem* item) +{ + switch (item->getType()) + { + case DbTreeItem::Type::DIR: + treeModel->deleteGroup(item); + break; + case DbTreeItem::Type::DB: + DBLIST->removeDb(item->getDb()); + break; + case DbTreeItem::Type::TABLE: + case DbTreeItem::Type::VIRTUAL_TABLE: + case DbTreeItem::Type::INDEX: + case DbTreeItem::Type::TRIGGER: + case DbTreeItem::Type::VIEW: + { + Db* db = item->getDb(); + DbObjectDialogs dialogs(db); + dialogs.setNoConfirmation(true); // confirmation is done in deleteSelected() + dialogs.setNoSchemaRefreshing(true); // we will refresh after all items are deleted + dialogs.dropObject(item->text()); // TODO database name when supported + break; + } + case DbTreeItem::Type::TABLES: + case DbTreeItem::Type::INDEXES: + case DbTreeItem::Type::TRIGGERS: + case DbTreeItem::Type::VIEWS: + case DbTreeItem::Type::COLUMNS: + case DbTreeItem::Type::COLUMN: + case DbTreeItem::Type::ITEM_PROTOTYPE: + break; + } +} + + +void DbTree::refreshSchema(Db* db) +{ + if (!db) + return; + + if (!db->isOpen()) + return; + + treeModel->refreshSchema(db); +} + +void DbTree::copy() +{ + QMimeData* mimeData = treeModel->mimeData(ui->treeView->getSelectedIndexes()); + QApplication::clipboard()->setMimeData(mimeData); +} + +void DbTree::paste() +{ + DbTreeItem* currItem = ui->treeView->currentItem(); + QModelIndex idx; + if (currItem) + idx = currItem->index(); + + treeModel->pasteData(QApplication::clipboard()->mimeData(), -1, -1, idx, Qt::CopyAction); +} + +void DbTree::selectAll() +{ + ui->treeView->selectAll(); +} + +void DbTree::createGroup() +{ + QStringList existingItems; + QStandardItem* currItem = ui->treeView->getItemForAction(true); + DbTreeItem* itemToMove = nullptr; + if (currItem) + { + // Look for any directory in the path to the root, starting with the current item + do + { + if (dynamic_cast(currItem)->getType() == DbTreeItem::Type::DIR) + { + existingItems = dynamic_cast(currItem)->childNames(); + break; + } + else + { + itemToMove = dynamic_cast(currItem); + } + } + while ((currItem = currItem->parent()) != nullptr); + } + + // No luck? Use root. + if (!currItem) + currItem = treeModel->root(); + + QString name = ""; + while (existingItems.contains(name = QInputDialog::getText(this, tr("Create group"), tr("Group name"))) || + (name.isEmpty() && !name.isNull())) + { + QMessageBox::information(this, tr("Create directory"), tr("Entry with name %1 already exists in directory %2.") + .arg(name).arg(currItem->text()), QMessageBox::Ok); + } + + if (name.isNull()) + return; + + DbTreeItem* newDir = treeModel->createGroup(name, currItem); + if (itemToMove) + treeModel->move(itemToMove, newDir); +} + +void DbTree::deleteGroup() +{ + DbTreeItem* item = ui->treeView->getItemForAction(); + if (!item) + return; + + QMessageBox::StandardButton resp = QMessageBox::question(this, tr("Delete group"), + tr("Are you sure you want to delete group %1?\nAll objects from this group will be moved to parent group.").arg(item->text().left(ITEM_TEXT_LIMIT))); + + if (resp != QMessageBox::Yes) + return; + + treeModel->deleteGroup(item); +} + +void DbTree::renameGroup() +{ + DbTreeItem* item = ui->treeView->getItemForAction(); + if (!item) + return; + + ui->treeView->edit(item->index()); +} + +void DbTree::addDb() +{ + DbTreeItem* currItem = ui->treeView->getItemForAction(); + + DbDialog dialog(DbDialog::ADD, this); + if (!dialog.exec()) + return; + + QString name = dialog.getName(); + + // If we created db in some group, move it there + if (currItem && currItem->getType() == DbTreeItem::Type::DIR) + { + DbTreeItem* dbItem = dynamic_cast(treeModel->findItem(DbTreeItem::Type::DB, name)); + if (!dbItem) + { + qWarning() << "Created and added db to tree, but could not find it while trying to move it to target group" << currItem->text(); + return; + } + treeModel->move(dbItem, currItem); + } +} + +void DbTree::editDb() +{ + Db* db = getSelectedDb(); + if (!db) + return; + + bool perm = CFG->isDbInConfig(db->getName()); + + DbDialog dialog(DbDialog::EDIT, this); + dialog.setDb(db); + dialog.setPermanent(perm); + dialog.exec(); +} + +void DbTree::removeDb() +{ + Db* db = getSelectedDb(); + if (!db) + return; + + QMessageBox::StandardButton result = QMessageBox::question(this, tr("Delete database"), tr("Are you sure you want to delete database '%1'?").arg(db->getName().left(ITEM_TEXT_LIMIT))); + if (result != QMessageBox::Yes) + return; + + DBLIST->removeDb(db); +} + +void DbTree::connectToDb() +{ + Db* db = getSelectedDb(); + if (!db) + return; + + if (db->isOpen()) + return; + + db->open(); +} + +void DbTree::disconnectFromDb() +{ + Db* db = getSelectedDb(); + if (!db) + return; + + if (!db->isOpen()) + return; + + db->close(); +} + + +void DbTree::import() +{ + if (!ImportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot import, because no import plugin is loaded.")); + return; + } + + ImportDialog dialog(this); + Db* db = getSelectedDb(); + if (db) + dialog.setDb(db); + + dialog.exec(); +} + +void DbTree::exportDb() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + if (!ExportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot export, because no export plugin is loaded.")); + return; + } + + ExportDialog dialog(this); + dialog.setDatabaseMode(db); + dialog.exec(); +} + +void DbTree::addTable() +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + newTable(item); +} + +void DbTree::editTable() +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + if (table.isNull()) + { + qWarning() << "Tried to edit table, while table wasn't selected in DbTree."; + return; + } + + openTable(db, QString::null, table); // TODO put database name when supported +} + +void DbTree::delTable() +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + if (table.isNull()) + { + qWarning() << "Tried to drop table, while table wasn't selected in DbTree."; + return; + } + + DbObjectDialogs dialogs(db); + dialogs.dropObject(table); // TODO add database prefix when supported +} + +void DbTree::addIndex() +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + + DbObjectDialogs dialogs(db); + dialogs.addIndex(table); +} + +void DbTree::editIndex() +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString index = item->getIndex(); + + DbObjectDialogs dialogs(db); + dialogs.editIndex(index); +} + +void DbTree::delIndex() +{ + delSelectedObject(); +} + +void DbTree::addTrigger() +{ + Db* db = getSelectedOpenDb(); + if (!db) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + QString view = item->getView(); + + DbObjectDialogs dialogs(db); + dialogs.addTrigger(table, view); +} + +void DbTree::editTrigger() +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString trigger = item->getTrigger(); + + DbObjectDialogs dialogs(db); + dialogs.editTrigger(trigger); +} + +void DbTree::delTrigger() +{ + delSelectedObject(); +} + +void DbTree::addView() +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + newView(item); +} + +void DbTree::editView() +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString view = item->getView(); + if (view.isNull()) + { + qWarning() << "Tried to edit view, while view wasn't selected in DbTree."; + return; + } + + openView(db, QString(), view); // TODO handle named database when supported +} + +void DbTree::delView() +{ + delSelectedObject(); +} + +void DbTree::exportTable() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + if (table.isNull()) + { + qWarning() << "Tried to export table, while table wasn't selected in DbTree."; + return; + } + + if (!ExportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot export, because no export plugin is loaded.")); + return; + } + + ExportDialog dialog(this); + dialog.setTableMode(db, table); + dialog.exec(); +} + +void DbTree::importTable() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + if (table.isNull()) + { + qWarning() << "Tried to import into table, while table wasn't selected in DbTree."; + return; + } + + if (!ImportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot import, because no import plugin is loaded.")); + return; + } + + ImportDialog dialog(this); + dialog.setDbAndTable(db, table); + dialog.exec(); +} + +void DbTree::populateTable() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + if (table.isNull()) + { + qWarning() << "Tried to populate table, while table wasn't selected in DbTree."; + return; + } + + PopulateDialog dialog(this); + dialog.setDbAndTable(db, table); + dialog.exec(); +} + +void DbTree::addColumn() +{ + DbTreeItem* item = ui->treeView->currentItem(); + if (!item) + return; + + addColumn(item); +} + +void DbTree::editColumn() +{ + DbTreeItem* item = ui->treeView->currentItem(); + if (!item) + return; + + editColumn(item); +} + +void DbTree::delColumn() +{ + DbTreeItem* item = ui->treeView->currentItem(); + if (!item) + return; + + delColumn(item); +} + +void DbTree::convertDb() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + DbConverterDialog dialog(this); + dialog.setDb(db); + dialog.exec(); +} + +void DbTree::vacuumDb() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + SqlQueryPtr res = db->exec("VACUUM;"); + if (res->isError()) + notifyError(tr("Error while executing VACUUM on the database %1: %2").arg(db->getName(), res->getErrorText())); + else + notifyInfo(tr("VACUUM execution finished successfully.")); +} + +void DbTree::integrityCheck() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + EditorWindow* win = MAINWINDOW->openSqlEditor(); + if (!win->setCurrentDb(db)) + { + qCritical() << "Created EditorWindow had not got requested database:" << db->getName(); + win->close(); + return; + } + + win->getMdiWindow()->rename(tr("Integrity check (%1)").arg(db->getName())); + win->setContents("PRAGMA integrity_check;"); + win->execute(); +} + +void DbTree::createSimilarTable() +{ + Db* db = getSelectedDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* item = ui->treeView->currentItem(); + QString table = item->getTable(); + if (table.isNull()) + { + qWarning() << "Tried to clone table, while table wasn't selected in DbTree."; + return; + } + + DbObjectDialogs dialog(db); + dialog.addTableSimilarTo(QString(), table); +} + +void DbTree::addColumn(DbTreeItem* item) +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + DbTreeItem* tableItem = nullptr; + + if (item->getType() == DbTreeItem::Type::TABLE) + tableItem = item; + else + tableItem = item->findParentItem(DbTreeItem::Type::TABLE); + + if (!tableItem) + return; + + TableWindow* tableWin = openTable(tableItem); + tableWin->addColumn(); +} + +void DbTree::editColumn(DbTreeItem* item) +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + if (item->getType() != DbTreeItem::Type::COLUMN) + return; + + DbTreeItem* tableItem = item->findParentItem(DbTreeItem::Type::TABLE); + if (!tableItem) + return; + + TableWindow* tableWin = openTable(tableItem); + tableWin->editColumn(item->text()); +} + +void DbTree::delColumn(DbTreeItem* item) +{ + Db* db = getSelectedOpenDb(); + if (!db || !db->isValid()) + return; + + if (item->getType() != DbTreeItem::Type::COLUMN) + return; + + DbTreeItem* tableItem = item->findParentItem(DbTreeItem::Type::TABLE); + if (!tableItem) + return; + + TableWindow* tableWin = openTable(tableItem); + tableWin->delColumn(item->text()); +} + +void DbTree::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + UNUSED(previous); + updateActionStates(treeModel->itemFromIndex(current)); +} + +void DbTree::deleteSelected() +{ + QModelIndexList idxList = ui->treeView->getSelectedIndexes(); + QList items; + foreach (const QModelIndex& idx, idxList) + items << dynamic_cast(treeModel->itemFromIndex(idx)); + + deleteItems(items); +} + +void DbTree::deleteItems(const QList& itemsToDelete) +{ + QList items = itemsToDelete; + + filterUndeletableItems(items); + filterItemsWithParentInList(items); + + // Warning user about items to be deleted + static const QString itemTmp = " %2"; + + QStringList toDelete; + QStringList databasesToRemove; + QString itemStr; + int groupItems = 0; + foreach (DbTreeItem* item, items) + { + itemStr = itemTmp.arg(item->getIcon()->toUrl()).arg(item->text().left(ITEM_TEXT_LIMIT)); + + if (item->getType() == DbTreeItem::Type::DB) + databasesToRemove << itemStr; + else + toDelete << itemStr; + + if (item->getType() == DbTreeItem::Type::DIR) + groupItems++; + } + + QStringList actions; + if (toDelete.size() > 0) + actions << tr("Following objects will be deleted: %1.").arg(toDelete.join(", ")); + + if (databasesToRemove.size() > 0) + actions << tr("Following databases will be removed from list: %1.").arg(databasesToRemove.join(", ")); + + if (groupItems > 0) + actions << tr("Remainig objects from deleted group will be moved in place where the group used to be."); + + QString msg = tr("%1

Are you sure you want to continue?").arg(actions.join("

")); + + QMessageBox::StandardButton result = QMessageBox::question(this, tr("Delete objects"), msg); + if (result != QMessageBox::Yes) + return; + + // Deleting items + QSet databasesToRefresh; + for (DbTreeItem* item : items) + { + databasesToRefresh << item->getDb(); + deleteItem(item); + } + + for (Db* dbToRefresh : databasesToRefresh) + DBTREE->refreshSchema(dbToRefresh); +} + +void DbTree::refreshSchemas() +{ + foreach (Db* db, DBLIST->getDbList()) + treeModel->refreshSchema(db); +} + +void DbTree::interrupt() +{ + treeModel->interrupt(); +} + +void DbTree::refreshSchema() +{ + Db* db = getSelectedDb(); + refreshSchema(db); +} + +void DbTree::updateActionsForCurrent() +{ + updateActionStates(ui->treeView->currentItem()); +} + +void DbTree::dbConnected(Db* db) +{ + updateActionsForCurrent(); + updateDbIcon(db); +} + +void DbTree::dbDisconnected(Db* db) +{ + updateActionsForCurrent(); + updateDbIcon(db); +} + +void DbTree::updateDbIcon(Db* db) +{ + DbTreeItem* item = treeModel->findItem(DbTreeItem::Type::DB, db); + if (item) + item->updateDbIcon(); +} + +void DbTree::refreshFont() +{ + ui->treeView->doItemsLayout(); +} + +void DbTree::setupDefShortcuts() +{ + setShortcutContext({ + CLEAR_FILTER, DEL_SELECTED, REFRESH_SCHEMA, REFRESH_SCHEMAS, + ADD_DB, SELECT_ALL, COPY, PASTE + }, Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(DbTree, Action); +} + +int qHash(DbTree::Action action) +{ + return static_cast(action); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h new file mode 100644 index 0000000..b368c08 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h @@ -0,0 +1,205 @@ +#ifndef DBTREE_H +#define DBTREE_H + +#include "db/db.h" +#include "common/extactioncontainer.h" +#include "mainwindow.h" +#include "dbtree/dbtreeitem.h" +#include "guiSQLiteStudio_global.h" +#include + +class WidgetCover; +class QAction; +class QMenu; +class DbTreeModel; +class QStandardItem; +class QTimer; +class TableWindow; +class ViewWindow; +class UserInputFilter; +class DbTreeView; + +namespace Ui { + class DbTree; +} + +CFG_KEY_LIST(DbTree, QObject::tr("Database list"), + CFG_KEY_ENTRY(DEL_SELECTED, Qt::Key_Delete, QObject::tr("Delete selected item")) + CFG_KEY_ENTRY(CLEAR_FILTER, Qt::Key_Escape, QObject::tr("Clear filter contents")) + CFG_KEY_ENTRY(REFRESH_SCHEMA, Qt::Key_F5, QObject::tr("Refresh schema")) + CFG_KEY_ENTRY(REFRESH_SCHEMAS, Qt::SHIFT + Qt::Key_F5, QObject::tr("Refresh all schemas")) + CFG_KEY_ENTRY(ADD_DB, Qt::CTRL + Qt::Key_O, QObject::tr("Add database")) + CFG_KEY_ENTRY(SELECT_ALL, Qt::CTRL + Qt::Key_A, QObject::tr("Select all items")) + CFG_KEY_ENTRY(COPY, Qt::CTRL + Qt::Key_C, QObject::tr("Copy selected item(s)")) + CFG_KEY_ENTRY(PASTE, Qt::CTRL + Qt::Key_V, QObject::tr("Paste from clipboard")) +) + +class GUI_API_EXPORT DbTree : public QDockWidget, public ExtActionContainer +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + friend class DbTreeView; + + enum Action + { + COPY, + PASTE, + SELECT_ALL, + DEL_SELECTED, + CREATE_GROUP, + DELETE_GROUP, + RENAME_GROUP, + ADD_DB, + EDIT_DB, + DELETE_DB, + CONNECT_TO_DB, + DISCONNECT_FROM_DB, + IMPORT_INTO_DB, + EXPORT_DB, + CONVERT_DB, + VACUUM_DB, + INTEGRITY_CHECK, + ADD_TABLE, + EDIT_TABLE, + DEL_TABLE, + EXPORT_TABLE, + IMPORT_TABLE, + POPULATE_TABLE, + ADD_INDEX, + EDIT_INDEX, + DEL_INDEX, + ADD_TRIGGER, + EDIT_TRIGGER, + DEL_TRIGGER, + ADD_VIEW, + EDIT_VIEW, + DEL_VIEW, + ADD_COLUMN, + EDIT_COLUMN, + DEL_COLUMN, + CLEAR_FILTER, + REFRESH_SCHEMAS, + REFRESH_SCHEMA, + CREATE_SIMILAR_TABLE, + _separator // Never use it directly, it's just for menu setup + }; + + enum ToolBar + { + }; + + explicit DbTree(QWidget *parent = 0); + ~DbTree(); + + static void staticInit(); + + void init(); + void updateActionStates(const QStandardItem* item); + void setupActionsForMenu(DbTreeItem* currItem, QMenu* contextMenu); + QVariant saveSession(); + void restoreSession(const QVariant& sessionValue); + DbTreeModel* getModel() const; + DbTreeView* getView() const; + void showWidgetCover(); + void hideWidgetCover(); + void setSelectedItem(DbTreeItem* item); + bool isMimeDataValidForItem(const QMimeData* mimeData, const DbTreeItem* item); + QToolBar* getToolBar(int toolbar) const; + + static bool isItemDraggable(const DbTreeItem* item); + + protected: + void createActions(); + void setupDefShortcuts(); + + private: + void setActionEnabled(int action, bool enabled); + Db* getSelectedDb(); + Db* getSelectedOpenDb(); + TableWindow* openTable(DbTreeItem* item); + TableWindow* openTable(Db* db, const QString& database, const QString& table); + TableWindow* newTable(DbTreeItem* item); + ViewWindow* openView(DbTreeItem* item); + ViewWindow* openView(Db* db, const QString& database, const QString& view); + ViewWindow* newView(DbTreeItem* item); + void editIndex(DbTreeItem* item); + void editTrigger(DbTreeItem* item); + void delSelectedObject(); + void filterUndeletableItems(QList& items); + void filterItemsWithParentInList(QList& items); + void deleteItem(DbTreeItem* item); + static bool areDbTreeItemsValidForItem(QList srcItems, const DbTreeItem* dstItem); + static bool areUrlsValidForItem(const QList& srcUrls, const DbTreeItem* dstItem); + + static void initDndTypes(); + + Ui::DbTree *ui = nullptr; + DbTreeModel* treeModel = nullptr; + WidgetCover* widgetCover = nullptr; + + static QHash> allowedTypesInside; + static QSet draggableTypes; + static const constexpr int ITEM_TEXT_LIMIT = 300; + + public slots: + void refreshSchema(Db* db); + void refreshSchemas(); + void interrupt(); + + private slots: + void copy(); + void paste(); + void selectAll(); + void createGroup(); + void deleteGroup(); + void renameGroup(); + void addDb(); + void editDb(); + void removeDb(); + void connectToDb(); + void disconnectFromDb(); + void import(); + void exportDb(); + void addTable(); + void editTable(); + void delTable(); + void addIndex(); + void editIndex(); + void delIndex(); + void addTrigger(); + void editTrigger(); + void delTrigger(); + void addView(); + void editView(); + void delView(); + void exportTable(); + void importTable(); + void populateTable(); + void addColumn(); + void editColumn(); + void delColumn(); + void convertDb(); + void vacuumDb(); + void integrityCheck(); + void createSimilarTable(); + void addColumn(DbTreeItem* item); + void editColumn(DbTreeItem* item); + void delColumn(DbTreeItem* item); + void currentChanged(const QModelIndex & current, const QModelIndex & previous); + void deleteSelected(); + void deleteItems(const QList& itemsToDelete); + void refreshSchema(); + void updateActionsForCurrent(); + void dbConnected(Db* db); + void dbDisconnected(Db* db); + void updateDbIcon(Db* db); + void refreshFont(); +}; + +int qHash(DbTree::Action action); + +#define DBTREE MainWindow::getInstance()->getDbTree() + +#endif // DBTREE_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui new file mode 100644 index 0000000..52b0c7b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui @@ -0,0 +1,90 @@ + + + DbTree + + + + 0 + 0 + 200 + 618 + + + + false + + + QDockWidget::AllDockWidgetFeatures + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Databases + + + + + + + + 0 + 0 + + + + true + + + true + + + QAbstractItemView::InternalMove + + + Qt::CopyAction + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter by name + + + + + + + + + + + + DbTreeView + QTreeView +
dbtree/dbtreeview.h
+
+
+ + nameFilter + treeView + + + +
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp new file mode 100644 index 0000000..ead5e3d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp @@ -0,0 +1,332 @@ +#include "dbtreeitem.h" +#include "iconmanager.h" +#include "dbtreemodel.h" +#include "services/dbmanager.h" +#include "dbtree.h" +#include +#include + +DbTreeItem::DbTreeItem(DbTreeItem::Type type, const Icon& icon, const QString& nodeName, QObject* parent) + : DbTreeItem(type, nodeName, parent) +{ + setIcon(icon); +} + +DbTreeItem::DbTreeItem(DbTreeItem::Type type, const QString& nodeName, QObject *parent) + : QObject(parent) +{ + setText(nodeName); + setType(type); + init(); +} + +DbTreeItem::DbTreeItem(const DbTreeItem& item) + : QObject(item.QObject::parent()), QStandardItem(item) +{ + init(); +} + +DbTreeItem::DbTreeItem() +{ + setType(Type::ITEM_PROTOTYPE); + init(); +} + +void DbTreeItem::initMeta() +{ + qRegisterMetaType("DbTreeItem*"); + qRegisterMetaTypeStreamOperators("DbTreeItem*"); +} + +DbTreeItem::Type DbTreeItem::getType() const +{ + return static_cast(type()); +} + +void DbTreeItem::setType(Type type) +{ + setData(static_cast(type), DataRole::TYPE); +} + +int DbTreeItem::type() const +{ + return data(DataRole::TYPE).toInt(); +} + +DbTreeItem* DbTreeItem::findItem(DbTreeItem::Type type, const QString& name) +{ + return DbTreeModel::findItem(this, type, name); +} + +QStandardItem* DbTreeItem::clone() const +{ + return new DbTreeItem(*this); +} + +QList DbTreeItem::childs() const +{ + QList results; + for (int i = 0; i < rowCount(); i++) + results += child(i); + + return results; +} + +QStringList DbTreeItem::childNames() const +{ + QStringList results; + for (int i = 0; i < rowCount(); i++) + results += child(i)->text(); + + return results; +} + +QString DbTreeItem::getTable() const +{ + const DbTreeItem* item = getParentItem(Type::TABLE); + if (!item) + return QString::null; + + return item->text(); +} + +QString DbTreeItem::getColumn() const +{ + if (getType() != Type::COLUMN) + return QString::null; + + return text(); +} + +QString DbTreeItem::getIndex() const +{ + const DbTreeItem* item = getParentItem(Type::INDEX); + if (!item) + return QString::null; + + return item->text(); +} + +QString DbTreeItem::getTrigger() const +{ + const DbTreeItem* item = getParentItem(Type::TRIGGER); + if (!item) + return QString::null; + + return item->text(); +} + +QString DbTreeItem::getView() const +{ + const DbTreeItem* item = getParentItem(Type::VIEW); + if (!item) + return QString::null; + + return item->text(); +} + +QStandardItem *DbTreeItem::parentItem() const +{ + if (!QStandardItem::parent()) + return model()->invisibleRootItem(); + + return QStandardItem::parent(); +} + +DbTreeItem *DbTreeItem::parentDbTreeItem() const +{ + QStandardItem* parentItem = QStandardItem::parent(); + if (!parentItem) + return nullptr; + + return dynamic_cast(parentItem); +} + +QList DbTreeItem::getPathToRoot() +{ + QList path; + getPathToRoot(path); + return path; +} + +QList DbTreeItem::getPathToParentItem(DbTreeItem::Type type) +{ + QList path; + getPathToParentItem(path, type); + return path; +} + +QList DbTreeItem::getPathToParentItem(DbTreeItem::Type type, const QString& name) +{ + QList path; + getPathToParentItem(path, type, name); + return path; +} + +DbTreeItem* DbTreeItem::findParentItem(DbTreeItem::Type type) +{ + DbTreeItem* parent = parentDbTreeItem(); + if (!parent) + return nullptr; + + if (parent->getType() == type) + return parent; + + return parent->findParentItem(type); +} + +DbTreeItem* DbTreeItem::findParentItem(DbTreeItem::Type type, const QString& name) +{ + DbTreeItem* parent = parentDbTreeItem(); + if (!parent) + return nullptr; + + if (parent->getType() == type && name == parent->text()) + return parent; + + return parent->findParentItem(type); +} + +void DbTreeItem::getPathToRoot(QList &path) +{ + path << this; + if (parentDbTreeItem()) + parentDbTreeItem()->getPathToRoot(path); +} + +QString DbTreeItem::signature() const +{ + QString sig; + if (parentDbTreeItem()) + sig += parentDbTreeItem()->signature() + "_"; + + sig += QString::number(type()) + "." + QString::fromLatin1(text().toUtf8().toBase64()); + return sig; +} + +void DbTreeItem::getPathToParentItem(QList& path, DbTreeItem::Type type) +{ + path << this; + if (getType() == type) + return; + + if (parentDbTreeItem()) + parentDbTreeItem()->getPathToParentItem(path, type); +} + +void DbTreeItem::getPathToParentItem(QList& path, DbTreeItem::Type type, const QString& name) +{ + path << this; + if (getType() == type && name == text()) + return; + + if (parentDbTreeItem()) + parentDbTreeItem()->getPathToParentItem(path, type, name); +} + +const DbTreeItem* DbTreeItem::getParentItem(DbTreeItem::Type type) const +{ + if (getType() == type) + return this; + + DbTreeItem* parent = parentDbTreeItem(); + if (parent) + return parent->getParentItem(type); + + return nullptr; +} + +Db* DbTreeItem::getDb() const +{ + QString dbName = data(DataRole::DB).toString(); + return DBLIST->getByName(dbName); +} + +void DbTreeItem::setDb(Db* value) +{ + setDb(value->getName()); +} + +void DbTreeItem::setDb(const QString& dbName) +{ + setData(dbName, DataRole::DB); + updateDbIcon(); +} + +void DbTreeItem::updateDbIcon() +{ + if (getType() != DbTreeItem::Type::DB) + return; + + Db* db = getDb(); + if (db->isValid()) + { + if (db->isOpen()) + setIcon(ICONS.DATABASE_ONLINE); + else + setIcon(ICONS.DATABASE_OFFLINE); + } + else + setIcon(ICONS.DATABASE_INVALID); +} + +const Icon* DbTreeItem::getIcon() const +{ + return data(DataRole::ICON_PTR).value(); +} + +void DbTreeItem::setHidden(bool hidden) +{ + setData(hidden, DataRole::HIDDEN); + dynamic_cast(model())->itemChangedVisibility(this); +} + +bool DbTreeItem::isHidden() const +{ + return data(DataRole::HIDDEN).toBool(); +} + +void DbTreeItem::setIcon(const Icon& icon) +{ + setData(QVariant::fromValue(&icon), DataRole::ICON_PTR); + if (!icon.isNull()) + QStandardItem::setIcon(icon); +} + +void DbTreeItem::init() +{ + Type type = getType(); + if (type == Type::DIR) + setEditable(true); + else + setEditable(false); + + setData(false, DataRole::HIDDEN); + + Qt::ItemFlags f = flags(); + if (DbTree::isItemDraggable(this)) + f |= Qt::ItemIsDragEnabled; + else + f ^= Qt::ItemIsDragEnabled; + + setFlags(f); +} + +QDataStream &operator <<(QDataStream &out, const DbTreeItem *item) +{ + out << item->signature(); + return out; +} + +QDataStream &operator >>(QDataStream &in, DbTreeItem *&item) +{ + QString signature; + in >> signature; + item = DBTREE->getModel()->findItemBySignature(signature); + return in; +} + +int qHash(DbTreeItem::Type type) +{ + return static_cast(type); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h new file mode 100644 index 0000000..ba230f2 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h @@ -0,0 +1,112 @@ +#ifndef DBTREEITEM_H +#define DBTREEITEM_H + +#include "db/db.h" +#include "iconmanager.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class GUI_API_EXPORT DbTreeItem : public QObject, public QStandardItem +{ + Q_OBJECT + + public: + enum class Type + { + DIR = 1000, + DB = 1001, + TABLES = 1002, + TABLE = 1003, + INDEXES = 1004, + INDEX = 1005, + TRIGGERS = 1006, + TRIGGER = 1007, + VIEWS = 1008, + VIEW = 1009, + COLUMNS = 1010, + COLUMN = 1011, + VIRTUAL_TABLE = 1012, + ITEM_PROTOTYPE = 9999 + }; + + DbTreeItem(Type type, const Icon& icon, const QString& nodeName, QObject* parent = 0); + DbTreeItem(const DbTreeItem& item); + DbTreeItem(); + + static void initMeta(); + + int type() const; + DbTreeItem* findItem(Type type, const QString& name); + QStandardItem* clone() const; + QList childs() const; + QStringList childNames() const; + QString getTable() const; + QString getColumn() const; + QString getIndex() const; + QString getTrigger() const; + QString getView() const; + + /** + * @brief parentItem + * @return Parent item for this item. Might be the "invisible root item" if this is the top level item. It will never be null. + */ + QStandardItem* parentItem() const; + + /** + * @brief parentDbTreeItem + * @return Parent item that is always DbTreeItem. If there is no parent item (i.e. this is the top item), then null is returned. + */ + DbTreeItem* parentDbTreeItem() const; + QList getPathToRoot(); + QList getPathToParentItem(Type type); + QList getPathToParentItem(Type type, const QString& name); + DbTreeItem* findParentItem(Type type); + DbTreeItem* findParentItem(Type type, const QString& name); + QString signature() const; + + Type getType() const; + void setType(Type type); + Db* getDb() const; + void setDb(Db* value); + void setDb(const QString& dbName); + void updateDbIcon(); + const Icon* getIcon() const; + void setHidden(bool hidden); + bool isHidden() const; + void setIcon(const Icon& icon); + + private: + struct DataRole // not 'enum class' because we need autocasting to int for this one + { + enum Enum + { + TYPE = 1001, + DB = 1002, + ICON_PTR = 1003, + HIDDEN = 1004 + }; + }; + + DbTreeItem(Type type, const QString& nodeName, QObject* parent = 0); + + void init(); + void getPathToRoot(QList& path); + void getPathToParentItem(QList& path, Type type); + void getPathToParentItem(QList& path, Type type, const QString& name); + const DbTreeItem* getParentItem(Type type) const; + + signals: + + public slots: + +}; + +GUI_API_EXPORT QDataStream &operator<<(QDataStream &out, const DbTreeItem* item); +GUI_API_EXPORT QDataStream &operator>>(QDataStream &in, DbTreeItem*& item); + +GUI_API_EXPORT int qHash(DbTreeItem::Type type); + +Q_DECLARE_METATYPE(DbTreeItem*) + +#endif // DBTREEITEM_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp new file mode 100644 index 0000000..ef691d2 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp @@ -0,0 +1,164 @@ +#include "dbtreeitemdelegate.h" +#include "dbtreeitem.h" +#include "dbtreemodel.h" +#include "common/utils_sql.h" +#include "uiconfig.h" +#include "dbtree.h" +#include "dbtreeview.h" +#include +#include + +DbTreeItemDelegate::DbTreeItemDelegate(QObject *parent) : + QStyledItemDelegate(parent) +{ +} + +QSize DbTreeItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QSize size = QStyledItemDelegate::sizeHint(option, index); + + QFont f = CFG_UI.Fonts.DbTree.get(); + QFontMetrics fm(f); + size.setHeight(qMax(18, fm.height())); + return size; +} + +void DbTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + const DbTreeModel* model = dynamic_cast(index.model()); + DbTreeItem* item = dynamic_cast(model->itemFromIndex(index)); + + opt.font = CFG_UI.Fonts.DbTree.get(); + opt.fontMetrics = QFontMetrics(opt.font); + + QModelIndex currIndex = DBTREE->getView()->selectionModel()->currentIndex(); + if (currIndex.isValid() && item->index() == currIndex) + opt.state |= QStyle::State_HasFocus; + + QStyledItemDelegate::paint(painter, opt, index); + + if (!CFG_UI.General.ShowDbTreeLabels.get()) + return; + + switch (item->getType()) + { + case DbTreeItem::Type::DIR: + break; + case DbTreeItem::Type::DB: + paintDb(painter, opt, index, item); + break; + case DbTreeItem::Type::TABLES: + case DbTreeItem::Type::INDEXES: + case DbTreeItem::Type::TRIGGERS: + case DbTreeItem::Type::VIEWS: + case DbTreeItem::Type::COLUMNS: + paintChildCount(painter, opt, index, item); + break; + case DbTreeItem::Type::TABLE: + paintTableLabel(painter, opt, index, item); + break; + case DbTreeItem::Type::VIRTUAL_TABLE: + paintVirtualTableLabel(painter, opt, index, item); + break; + case DbTreeItem::Type::INDEX: + paintSystemIndexLabel(painter, opt, index, item); + break; + case DbTreeItem::Type::TRIGGER: + case DbTreeItem::Type::VIEW: + case DbTreeItem::Type::COLUMN: + case DbTreeItem::Type::ITEM_PROTOTYPE: + break; + } +} + +void DbTreeItemDelegate::paintDb(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item) const +{ + static const QString versionStringTemplate = QStringLiteral("(%1)"); + QString versionString = versionStringTemplate.arg("?"); + Db* db = item->getDb(); + if (!db) + return; + + if (db->isValid()) + { + QString t = db->getTypeLabel(); + versionString = versionStringTemplate.arg(t); + } + else + { + versionString = versionStringTemplate.arg(tr("error", "dbtree labels")); + } + + paintLabel(painter, option, index, item, versionString); +} + +void DbTreeItemDelegate::paintChildCount(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item) const +{ + int cnt = item->rowCount(); + if (cnt > 0) + paintLabel(painter, option, index, item, QString("(%1)").arg(cnt)); +} + +void DbTreeItemDelegate::paintTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const +{ + if (isSystemTable(item->text())) + { + paintLabel(painter, option, index, item, tr("(system table)", "database tree label")); + return; + } + + if (!CFG_UI.General.ShowRegularTableLabels.get()) + return; + + int columnsCount = item->child(0)->rowCount(); + int indexesCount = item->child(1)->rowCount(); + int triggersCount = item->child(2)->rowCount(); + paintLabel(painter, option, index, item, QString("(%1, %2, %3)").arg(columnsCount).arg(indexesCount).arg(triggersCount)); +} + +void DbTreeItemDelegate::paintVirtualTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const +{ + if (!CFG_UI.General.ShowVirtualTableLabels.get()) + return; + + paintLabel(painter, option, index, item, tr("(virtual)", "virtual table label")); +} + +void DbTreeItemDelegate::paintSystemIndexLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const +{ + Db* db = item->getDb(); + if (!db || !db->isValid()) + return; + + if (!isSystemIndex(item->text(), db->getDialect())) + return; + + paintLabel(painter, option, index, item, tr("(system index)", "database tree label")); +} + +void DbTreeItemDelegate::paintLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item, const QString &label) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + painter->save(); + + // Colors + painter->setPen(CFG_UI.Colors.DbTreeLabelsFg.get()); + + // Font + opt.font = CFG_UI.Fonts.DbTreeLabel.get(); + opt.fontMetrics = QFontMetrics(opt.font); + painter->setFont(opt.font); + + // Coords + int x = option.rect.x() + option.fontMetrics.width(item->text()) + 15 + option.decorationSize.width(); + int y = opt.rect.y() + (opt.rect.height() - opt.fontMetrics.descent() - opt.fontMetrics.ascent()) / 2 + opt.fontMetrics.ascent(); + + // Paint + painter->drawText(QPoint(x, y), label); + painter->restore(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h new file mode 100644 index 0000000..43eeac2 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h @@ -0,0 +1,28 @@ +#ifndef DBTREEITEMDELEGATE_H +#define DBTREEITEMDELEGATE_H + +#include "guiSQLiteStudio_global.h" +#include + +class DbTreeItem; + +class GUI_API_EXPORT DbTreeItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT + + public: + explicit DbTreeItemDelegate(QObject *parent = 0); + + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + private: + void paintDb(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const; + void paintChildCount(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const; + void paintTableLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const; + void paintVirtualTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const; + void paintSystemIndexLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const; + void paintLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item, const QString& label) const; +}; + +#endif // DBTREEITEMDELEGATE_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp new file mode 100644 index 0000000..b112713 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp @@ -0,0 +1,74 @@ +#include "dbtreeitemfactory.h" +#include "iconmanager.h" +#include "common/unused.h" + +DbTreeItem *DbTreeItemFactory::createDir(const QString &name, QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::DIR, ICONS.DIRECTORY, name, parent); +} + +DbTreeItem *DbTreeItemFactory::createDb(const QString &name, QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::DB, ICONS.DATABASE_OFFLINE, name, parent); +} + +DbTreeItem *DbTreeItemFactory::createTable(const QString &name, QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::TABLE, ICONS.TABLE, name, parent); +} + +DbTreeItem* DbTreeItemFactory::createVirtualTable(const QString& name, QObject* parent) +{ + return new DbTreeItem(DbTreeItem::Type::VIRTUAL_TABLE, ICONS.VIRTUAL_TABLE, name, parent); +} + +DbTreeItem *DbTreeItemFactory::createIndex(const QString &name, QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::INDEX, ICONS.INDEX, name, parent); +} + +DbTreeItem *DbTreeItemFactory::createTrigger(const QString &name, QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::TRIGGER, ICONS.TRIGGER, name, parent); +} + +DbTreeItem *DbTreeItemFactory::createView(const QString &name, QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::VIEW, ICONS.VIEW, name, parent); +} + +DbTreeItem *DbTreeItemFactory::createColumn(const QString &name, QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::COLUMN, ICONS.COLUMN, name, parent); +} + +DbTreeItem *DbTreeItemFactory::createTables(QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::TABLES, ICONS.TABLES, QObject::tr("Tables"), parent); +} + +DbTreeItem *DbTreeItemFactory::createIndexes(QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::INDEXES, ICONS.INDEXES, QObject::tr("Indexes"), parent); +} + +DbTreeItem *DbTreeItemFactory::createTriggers(QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::TRIGGERS, ICONS.TRIGGERS, QObject::tr("Triggers"), parent); +} + +DbTreeItem *DbTreeItemFactory::createViews(QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::VIEWS, ICONS.VIEWS, QObject::tr("Views"), parent); +} + +DbTreeItem *DbTreeItemFactory::createColumns(QObject *parent) +{ + return new DbTreeItem(DbTreeItem::Type::COLUMNS, ICONS.COLUMNS, QObject::tr("Columns"), parent); +} + +DbTreeItem *DbTreeItemFactory::createPrototype(QObject *parent) +{ + UNUSED(parent); + return new DbTreeItem(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h new file mode 100644 index 0000000..acb3aeb --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h @@ -0,0 +1,26 @@ +#ifndef DBTREEITEMFACTORY_H +#define DBTREEITEMFACTORY_H + +#include "guiSQLiteStudio_global.h" +#include "dbtree/dbtreeitem.h" + +class GUI_API_EXPORT DbTreeItemFactory +{ + public: + static DbTreeItem* createDir(const QString& name, QObject *parent = nullptr); + static DbTreeItem* createDb(const QString& name, QObject *parent = nullptr); + static DbTreeItem* createTable(const QString& name, QObject *parent = nullptr); + static DbTreeItem* createVirtualTable(const QString& name, QObject *parent = nullptr); + static DbTreeItem* createIndex(const QString& name, QObject *parent = nullptr); + static DbTreeItem* createTrigger(const QString& name, QObject *parent = nullptr); + static DbTreeItem* createView(const QString& name, QObject *parent = nullptr); + static DbTreeItem* createColumn(const QString& name, QObject *parent = nullptr); + static DbTreeItem* createTables(QObject *parent = nullptr); + static DbTreeItem* createIndexes(QObject *parent = nullptr); + static DbTreeItem* createTriggers(QObject *parent = nullptr); + static DbTreeItem* createViews(QObject *parent = nullptr); + static DbTreeItem* createColumns(QObject *parent = nullptr); + static DbTreeItem* createPrototype(QObject *parent = nullptr); +}; + +#endif // DBTREEITEMFACTORY_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp new file mode 100644 index 0000000..8a71a10 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp @@ -0,0 +1,1222 @@ +#include "dbtreemodel.h" +#include "services/dbmanager.h" +#include "dbtreeview.h" +#include "iconmanager.h" +#include "uiconfig.h" +#include "schemaresolver.h" +#include "dbtreeitemfactory.h" +#include "common/unused.h" +#include "services/pluginmanager.h" +#include "plugins/dbplugin.h" +#include "dbobjectorganizer.h" +#include "dialogs/dbdialog.h" +#include "dialogs/errorsconfirmdialog.h" +#include "dialogs/versionconvertsummarydialog.h" +#include "db/invaliddb.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const QString DbTreeModel::toolTipTableTmp = "
%1
"; +const QString DbTreeModel::toolTipHdrRowTmp = "%2"; +const QString DbTreeModel::toolTipRowTmp = "%1%2"; +const QString DbTreeModel::toolTipIconRowTmp = "%2%3"; + +DbTreeModel::DbTreeModel() +{ + setItemPrototype(DbTreeItemFactory::createPrototype()); + connectDbManagerSignals(); + + connect(CFG, SIGNAL(massSaveBegins()), this, SLOT(massSaveBegins())); + connect(CFG, SIGNAL(massSaveCommited()), this, SLOT(massSaveCommited())); + connect(CFG_UI.General.ShowSystemObjects, SIGNAL(changed(QVariant)), this, SLOT(markSchemaReloadingRequired())); + + dbOrganizer = new DbObjectOrganizer(confirmReferencedTables, resolveNameConflict, confirmConversion, confirmConversionErrors); + dbOrganizer->setAutoDelete(false); + connect(dbOrganizer, SIGNAL(finishedDbObjectsCopy(bool,Db*,Db*)), this, SLOT(dbObjectsCopyFinished(bool,Db*,Db*))); + connect(dbOrganizer, SIGNAL(finishedDbObjectsMove(bool,Db*,Db*)), this, SLOT(dbObjectsMoveFinished(bool,Db*,Db*))); +} + +DbTreeModel::~DbTreeModel() +{ +} + +void DbTreeModel::connectDbManagerSignals() +{ + connect(DBLIST, SIGNAL(dbAdded(Db*)), this, SLOT(dbAdded(Db*))); + connect(DBLIST, SIGNAL(dbUpdated(QString,Db*)), this, SLOT(dbUpdated(QString,Db*))); + connect(DBLIST, SIGNAL(dbRemoved(Db*)), this, SLOT(dbRemoved(Db*))); + connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*))); + connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*))); + connect(DBLIST, SIGNAL(dbLoaded(Db*)), this, SLOT(dbLoaded(Db*))); + connect(DBLIST, SIGNAL(dbUnloaded(Db*)), this, SLOT(dbUnloaded(Db*))); +} + +void DbTreeModel::move(QStandardItem *itemToMove, QStandardItem *newParentItem, int newRow) +{ + QStandardItem* currParent = dynamic_cast(itemToMove)->parentItem(); + if (!newParentItem) + newParentItem = root(); + + if (newParentItem == currParent) + { + move(itemToMove, newRow); + return; + } + + int oldRow = itemToMove->index().row(); + currParent->takeRow(oldRow); + + if (newRow > currParent->rowCount() || newRow < 0) + newParentItem->appendRow(itemToMove); + else + newParentItem->insertRow(newRow, itemToMove); +} + +void DbTreeModel::move(QStandardItem *itemToMove, int newRow) +{ + QStandardItem* currParent = dynamic_cast(itemToMove)->parentItem(); + int oldRow = itemToMove->index().row(); + currParent->takeRow(oldRow); + if (newRow > currParent->rowCount() || newRow < 0) + currParent->appendRow(itemToMove); + else if (oldRow < newRow) + currParent->insertRow(newRow - 1, itemToMove); + else + currParent->insertRow(newRow, itemToMove); +} + +void DbTreeModel::deleteGroup(QStandardItem *groupItem) +{ + QStandardItem* parentItem = dynamic_cast(groupItem)->parentItem(); + if (!parentItem) + parentItem = root(); + + foreach (QStandardItem* child, dynamic_cast(groupItem)->childs()) + move(child, parentItem); + + parentItem->removeRow(groupItem->row()); +} + +DbTreeItem* DbTreeModel::createGroup(const QString& name, QStandardItem* parent) +{ + if (!parent) + parent = root(); + + DbTreeItem* item = DbTreeItemFactory::createDir(name, this); + parent->appendRow(item); + return item; +} + +QStringList DbTreeModel::getGroupFor(QStandardItem *item) +{ + QStringList group; + while ((item = item->parent()) != nullptr) + { + if (dynamic_cast(item)->getType() == DbTreeItem::Type::DIR) + group.prepend(item->text()); + } + return group; +} + +void DbTreeModel::applyFilter(const QString &filter) +{ + applyFilter(root(), filter); + currentFilter = filter; +} + +bool DbTreeModel::applyFilter(QStandardItem *parentItem, const QString &filter) +{ + bool empty = filter.isEmpty(); + bool visibilityForParent = false; + DbTreeItem* item = nullptr; + QModelIndex index; + bool subFilterResult; + bool matched; + for (int i = 0; i < parentItem->rowCount(); i++) + { + item = dynamic_cast(parentItem->child(i)); + index = item->index(); + subFilterResult = applyFilter(item, filter); + matched = empty || subFilterResult || item->text().contains(filter, Qt::CaseInsensitive); + treeView->setRowHidden(index.row(), index.parent(), !matched); + + if (matched) + visibilityForParent = true; + } + return visibilityForParent; +} + +void DbTreeModel::storeGroups() +{ + QList groups = childsToConfig(invisibleRootItem()); + CFG->storeGroups(groups); +} + +void DbTreeModel::readGroups(QList dbList) +{ + QList groups = CFG->getGroups(); + foreach (const Config::DbGroupPtr& group, groups) + restoreGroup(group, &dbList); + + // Add rest of databases, not mentioned in groups + Config::DbGroupPtr group; + foreach (Db* db, dbList) + { + group = Config::DbGroupPtr::create(); + group->referencedDbName = db->getName(); + restoreGroup(group); + } +} + +QList DbTreeModel::childsToConfig(QStandardItem *item) +{ + QList groups; + Config::DbGroupPtr group; + DbTreeItem* dbTreeItem = nullptr; + for (int i = 0; i < item->rowCount(); i++) + { + dbTreeItem = dynamic_cast(item->child(i)); + switch (dbTreeItem->getType()) + { + case DbTreeItem::Type::DIR: + { + group = Config::DbGroupPtr::create(); + group->name = dbTreeItem->text(); + group->order = i; + group->open = treeView->isExpanded(dbTreeItem->index()); + group->childs = childsToConfig(dbTreeItem); + groups += group; + break; + } + case DbTreeItem::Type::DB: + { + group = Config::DbGroupPtr::create(); + group->referencedDbName = dbTreeItem->text(); + group->order = i; + group->open = dbTreeItem->getDb()->isOpen(); + groups += group; + break; + } + default: + // no-op + break; + } + } + return groups; +} + +void DbTreeModel::restoreGroup(const Config::DbGroupPtr& group, QList* dbList, QStandardItem* parent) +{ + Db* db = nullptr; + DbTreeItem* item = nullptr; + if (group->referencedDbName.isNull()) + { + item = DbTreeItemFactory::createDir(group->name, this); + } + else + { + // If db is managed by manager, it means it was successfully loaded. + // Otherwise there was a problem with the file, or with plugin for that database + // and we still want to have dbtree item for that database, we will just hide it. + // Later, when plugin is loaded, item might become visible. + item = DbTreeItemFactory::createDb(group->referencedDbName, this); + item->setDb(group->referencedDbName); + + db = DBLIST->getByName(group->referencedDbName); + if (db && dbList) + dbList->removeOne(db); + } + + if (!parent) + parent = invisibleRootItem(); + + parent->appendRow(item); + + if (item->getType() == DbTreeItem::Type::DIR) + { + foreach (const Config::DbGroupPtr& childGroup, group->childs) + restoreGroup(childGroup, dbList, item); + } + + if (group->open) + { + if (db) + { + if (db->open()) + treeView->expand(item->index()); + } + else + { + treeView->expand(item->index()); + } + } +} + +void DbTreeModel::expanded(const QModelIndex &index) +{ + QStandardItem* item = itemFromIndex(index); + if (!item->hasChildren()) + { + treeView->collapse(index); + return; + } + + if (dynamic_cast(item)->getType() == DbTreeItem::Type::DIR) + itemFromIndex(index)->setIcon(ICONS.DIRECTORY_OPEN); +} + +void DbTreeModel::collapsed(const QModelIndex &index) +{ + QStandardItem* item = itemFromIndex(index); + if (dynamic_cast(item)->getType() == DbTreeItem::Type::DIR) + item->setIcon(ICONS.DIRECTORY_OPEN); +} + +void DbTreeModel::dbAdded(Db* db) +{ + DbTreeItem* item = DbTreeItemFactory::createDb(db->getName(), this); + item->setDb(db); + root()->appendRow(item); +} + +void DbTreeModel::dbUpdated(const QString& oldName, Db* db) +{ + DbTreeItem* item = dynamic_cast(findItem(DbTreeItem::Type::DB, oldName)); + if (!item) + { + qWarning() << "Updated database in db model that couldn't be found in the model:" << oldName; + return; + } + + item->setText(db->getName()); + item->setDb(db->getName()); +} + +void DbTreeModel::dbRemoved(Db* db) +{ + dbRemoved(db->getName()); +} + +void DbTreeModel::dbRemoved(const QString& name) +{ + QStandardItem* item = findItem(DbTreeItem::Type::DB, name); + if (!item) + { + qWarning() << "Removed database from db model that couldn't be found in the model:" << name; + return; + } + dbRemoved(item); +} + +void DbTreeModel::dbRemoved(QStandardItem* item) +{ + QStandardItem* parent = item->parent(); + if (!parent) + parent = root(); + + parent->removeRow(item->index().row()); + if (!parent->hasChildren()) + treeView->collapse(parent->index()); +} + +void DbTreeModel::interrupt() +{ + dbOrganizer->interrupt(); +} + +void DbTreeModel::refreshSchema(Db* db) +{ + QStandardItem* item = findItem(DbTreeItem::Type::DB, db); + if (!item) + { + qWarning() << "Refreshing schema of db that couldn't be found in the model:" << db->getName(); + return; + } + refreshSchema(db, item); + applyFilter(item, currentFilter); +} + +QList DbTreeModel::getAllItemsAsFlatList() const +{ + return getChildsAsFlatList(root()); +} + +QList DbTreeModel::getChildsAsFlatList(QStandardItem* item) const +{ + QList items; + QStandardItem* child = nullptr; + for (int i = 0; i < item->rowCount(); i++) + { + child = item->child(i); + items << dynamic_cast(child); + items += getChildsAsFlatList(child); + } + return items; +} + +QVariant DbTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QStandardItemModel::data(index, role);; + + DbTreeItem* item = dynamic_cast(itemFromIndex(index)); + switch (role) + { + case Qt::ToolTipRole: + { + return getToolTip(item); + } + } + return QStandardItemModel::data(index, role); +} + +QString DbTreeModel::getToolTip(DbTreeItem* item) const +{ + if (!item) + return QString::null; + + switch (item->getType()) + { + case DbTreeItem::Type::DB: + return getDbToolTip(item); + case DbTreeItem::Type::TABLE: + return getTableToolTip(item); + default: + break; + } + return QString::null; +} + +QString DbTreeModel::getDbToolTip(DbTreeItem* item) const +{ + QStringList rows; + + Db* db = item->getDb(); + QFile dbFile(db->getPath()); + QString iconPath = db->isValid() ? ICONS.DATABASE.toImgSrc() : ICONS.DATABASE_INVALID.toImgSrc(); + + rows << toolTipHdrRowTmp.arg(iconPath).arg(tr("Database: %1", "dbtree tooltip").arg(db->getName())); + rows << toolTipRowTmp.arg("URI:").arg(db->getPath()); + + if (db->isValid()) + { + rows << toolTipRowTmp.arg(tr("Version:", "dbtree tooltip")).arg(QString("SQLite %1").arg(db->getVersion())); + rows << toolTipRowTmp.arg(tr("File size:", "dbtree tooltip")).arg(formatFileSize(dbFile.size())); + rows << toolTipRowTmp.arg(tr("Encoding:", "dbtree tooltip")).arg(db->getEncoding()); + } + else + { + InvalidDb* idb = dynamic_cast(db); + rows << toolTipRowTmp.arg(tr("Error details:", "dbtree tooltip")).arg(idb->getError()); + } + + return toolTipTableTmp.arg(rows.join("")); +} + +QString DbTreeModel::getTableToolTip(DbTreeItem* item) const +{ + QStringList rows; + + rows << toolTipHdrRowTmp.arg(ICONS.TABLE.getPath()).arg(tr("Table : %1", "dbtree tooltip").arg(item->text())); + + QStandardItem* columnsItem = item->child(0); + QStandardItem* indexesItem = item->child(1); + QStandardItem* triggersItem = item->child(2); + + int columnCnt = columnsItem->rowCount(); + int indexesCount = indexesItem->rowCount(); + int triggersCount = triggersItem->rowCount(); + + QStringList columns; + for (int i = 0; i < columnCnt; i++) + columns << columnsItem->child(i)->text(); + + QStringList indexes; + for (int i = 0; i < indexesCount; i++) + indexes << indexesItem->child(i)->text(); + + QStringList triggers; + for (int i = 0; i < triggersCount; i++) + triggers << triggersItem->child(i)->text(); + + rows << toolTipIconRowTmp.arg(ICONS.COLUMN.getPath()) + .arg(tr("Columns (%1):", "dbtree tooltip").arg(columnCnt)) + .arg(columns.join(", ")); + rows << toolTipIconRowTmp.arg(ICONS.INDEX.getPath()) + .arg(tr("Indexes (%1):", "dbtree tooltip").arg(indexesCount)) + .arg(indexes.join(", ")); + rows << toolTipIconRowTmp.arg(ICONS.TRIGGER.getPath()) + .arg(tr("Triggers (%1):", "dbtree tooltip").arg(triggersCount)) + .arg(triggers.join(", ")); + + return toolTipTableTmp.arg(rows.join("")); +} + +void DbTreeModel::refreshSchema(Db* db, QStandardItem *item) +{ + if (!db->isOpen()) + return; + + // Remember expanded state of this branch + QHash expandedState; + collectExpandedState(expandedState, item); + + // Delete child nodes + while (item->rowCount() > 0) + item->removeRow(0); + + // Now prepare to create new branch + SchemaResolver resolver(db); + resolver.setIgnoreSystemObjects(!CFG_UI.General.ShowSystemObjects.get()); + + // Collect all db objects and build the db branch + bool sort = CFG_UI.General.SortObjects.get(); + QStringList tables = resolver.getTables(); + QStringList virtualTables; + for (const QString& table : tables) + { + if (resolver.isVirtualTable(table)) + virtualTables << table; + } + + QList tableItems = refreshSchemaTables(tables, virtualTables, sort); + StrHash> allTableColumns = refreshSchemaTableColumns(resolver.getAllTableColumns()); + StrHash> indexItems = refreshSchemaIndexes(resolver.getGroupedIndexes(), sort); + StrHash> triggerItems = refreshSchemaTriggers(resolver.getGroupedTriggers(), sort); + QList viewItems = refreshSchemaViews(resolver.getViews(), sort); + refreshSchemaBuild(item, tableItems, indexItems, triggerItems, viewItems, allTableColumns); + populateChildItemsWithDb(item, db); + restoreExpandedState(expandedState, item); +} + +void DbTreeModel::collectExpandedState(QHash &state, QStandardItem *parentItem) +{ + if (!parentItem) + parentItem = root(); + + DbTreeItem* dbTreeItem = dynamic_cast(parentItem); + if (dbTreeItem) + state[dbTreeItem->signature()] = treeView->isExpanded(dbTreeItem->index()); + + for (int i = 0; i < parentItem->rowCount(); i++) + collectExpandedState(state, parentItem->child(i)); +} + +QList DbTreeModel::refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort) +{ + QStringList sortedTables = tables; + if (sort) + qSort(sortedTables); + + QList items; + foreach (const QString& table, sortedTables) + { + if (virtualTables.contains(table)) + items += DbTreeItemFactory::createVirtualTable(table, this); + else + items += DbTreeItemFactory::createTable(table, this); + } + + return items; +} + +StrHash> DbTreeModel::refreshSchemaTableColumns(const StrHash &columns) +{ + QStringList sortedColumns; + bool sort = CFG_UI.General.SortColumns.get(); + StrHash> items; + for (const QString& key : columns.keys()) + { + sortedColumns = columns[key]; + if (sort) + qSort(sortedColumns); + + for (const QString& column : sortedColumns) + items[key] += DbTreeItemFactory::createColumn(column, this); + } + return items; +} + +StrHash > DbTreeModel::refreshSchemaIndexes(const StrHash &indexes, bool sort) +{ + StrHash > items; + QStringList sortedIndexes; + for (const QString& key : indexes.keys()) + { + sortedIndexes = indexes[key]; + if (sort) + qSort(sortedIndexes); + + for (const QString& index : sortedIndexes) + items[key] += DbTreeItemFactory::createIndex(index, this); + } + return items; +} + +StrHash> DbTreeModel::refreshSchemaTriggers(const StrHash &triggers, bool sort) +{ + StrHash> items; + QStringList sortedTriggers; + for (const QString& key : triggers.keys()) + { + sortedTriggers = triggers[key]; + if (sort) + qSort(sortedTriggers); + + for (const QString& trigger : sortedTriggers) + items[key] += DbTreeItemFactory::createTrigger(trigger, this); + } + return items; +} + +QList DbTreeModel::refreshSchemaViews(const QStringList &views, bool sort) +{ + QStringList sortedViews = views; + if (sort) + qSort(sortedViews); + + QList items; + foreach (const QString& view, views) + items += DbTreeItemFactory::createView(view, this); + + return items; +} + +void DbTreeModel::populateChildItemsWithDb(QStandardItem *parentItem, Db* db) +{ + QStandardItem* childItem = nullptr; + for (int i = 0; i < parentItem->rowCount(); i++) + { + childItem = parentItem->child(i); + dynamic_cast(childItem)->setDb(db); + populateChildItemsWithDb(childItem, db); + } +} + +void DbTreeModel::refreshSchemaBuild(QStandardItem *dbItem, + QList tables, + StrHash > indexes, + StrHash > triggers, + QList views, + StrHash > allTableColumns) +{ + DbTreeItem* tablesItem = DbTreeItemFactory::createTables(this); + DbTreeItem* viewsItem = DbTreeItemFactory::createViews(this); + + dbItem->appendRow(tablesItem); + dbItem->appendRow(viewsItem); + + DbTreeItem* columnsItem = nullptr; + DbTreeItem* indexesItem = nullptr; + DbTreeItem* triggersItem = nullptr; + foreach (QStandardItem* tableItem, tables) + { + tablesItem->appendRow(tableItem); + + columnsItem = DbTreeItemFactory::createColumns(this); + indexesItem = DbTreeItemFactory::createIndexes(this); + triggersItem = DbTreeItemFactory::createTriggers(this); + + tableItem->appendRow(columnsItem); + tableItem->appendRow(indexesItem); + tableItem->appendRow(triggersItem); + + foreach (QStandardItem* columnItem, allTableColumns[tableItem->text()]) + columnsItem->appendRow(columnItem); + + foreach (QStandardItem* indexItem, indexes[tableItem->text()]) + indexesItem->appendRow(indexItem); + + foreach (QStandardItem* triggerItem, triggers[tableItem->text()]) + triggersItem->appendRow(triggerItem); + } + foreach (QStandardItem* viewItem, views) + { + viewsItem->appendRow(viewItem); + + triggersItem = DbTreeItemFactory::createTriggers(this); + viewItem->appendRow(triggersItem); + foreach (QStandardItem* triggerItem, triggers[viewItem->text()]) + triggersItem->appendRow(triggerItem); + } +} + +void DbTreeModel::restoreExpandedState(const QHash& expandedState, QStandardItem* parentItem) +{ + DbTreeItem* parentDbTreeItem = dynamic_cast(parentItem); + QString sig = parentDbTreeItem->signature(); + if (expandedState.contains(sig) && expandedState[sig]) + treeView->expand(parentItem->index()); + + foreach (QStandardItem* child, parentDbTreeItem->childs()) + restoreExpandedState(expandedState, child); +} + +void DbTreeModel::dbConnected(Db* db) +{ + QStandardItem* item = findItem(DbTreeItem::Type::DB, db); + if (!item) + { + qWarning() << "Connected to db that couldn't be found in the model:" << db->getName(); + return; + } + refreshSchema(db, item); + treeView->expand(item->index()); + if (CFG_UI.General.ExpandTables.get()) + treeView->expand(item->index().child(0, 0)); // also expand tables + + if (CFG_UI.General.ExpandViews.get()) + treeView->expand(item->index().child(1, 0)); // also expand views +} + +void DbTreeModel::dbDisconnected(Db* db) +{ + QStandardItem* item = findItem(DbTreeItem::Type::DB, db); + if (!item) + { + qWarning() << "Disconnected from db that couldn't be found in the model:" << db->getName(); + return; + } + + while (item->rowCount() > 0) + item->removeRow(0); + + treeView->collapse(item->index()); +} + +void DbTreeModel::dbUnloaded(Db* db) +{ + DbTreeItem* item = findItem(DbTreeItem::Type::DB, db->getName()); + if (!item) + { + qCritical() << "No DB item found to update icon:" << db->getName(); + return; + } + item->updateDbIcon(); +} + +void DbTreeModel::dbLoaded(Db* db) +{ + if (ignoreDbLoadedSignal) + return; + + DbTreeItem* item = findItem(DbTreeItem::Type::DB, db->getName()); + if (!item) + { + qCritical() << "No DB item found to update icon:" << db->getName(); + return; + } + item->updateDbIcon(); +} + +void DbTreeModel::massSaveBegins() +{ + requireSchemaReloading = false; +} + +void DbTreeModel::massSaveCommited() +{ + if (requireSchemaReloading) + { + for (Db* db : DBLIST->getDbList()) + { + if (db->isOpen()) + refreshSchema(db); + } + } +} + +void DbTreeModel::markSchemaReloadingRequired() +{ + requireSchemaReloading = true; +} + +DbTreeItem* DbTreeModel::findItem(DbTreeItem::Type type, const QString &name) +{ + return findItem(root(), type, name); +} + +DbTreeItem *DbTreeModel::findItem(QStandardItem* parentItem, DbTreeItem::Type type, const QString& name) +{ + DbTreeItem* item = nullptr; + DbTreeItem* subItem = nullptr; + for (int i = 0; i < parentItem->rowCount(); i++) + { + item = dynamic_cast(parentItem->child(i)); + + // Search recursively + if (item->hasChildren()) + { + subItem = findItem(item, type, name); + if (subItem) + return subItem; + } + + if (item->getType() != type) + continue; + + if (item->text() != name) + continue; + + return item; + } + + return nullptr; +} + +DbTreeItem *DbTreeModel::findItem(DbTreeItem::Type type, Db* db) +{ + return findItem(root(), type, db); +} + +DbTreeItem *DbTreeModel::findItemBySignature(const QString &signature) +{ + QStringList parts = signature.split("_"); + QStringList pair; + DbTreeItem* currItem = nullptr; + DbTreeItem::Type type; + QString name; + for (const QString& part : parts) + { + pair = part.split("."); + type = static_cast(pair.first().toInt()); + name = QString::fromUtf8(QByteArray::fromBase64(pair.last().toLatin1())); + currItem = findItem((currItem ? currItem : root()), type, name); + if (!currItem) + return nullptr; // not found the target item + } + return currItem; +} + +QList DbTreeModel::findItems(DbTreeItem::Type type) +{ + return findItems(root(), type); +} + +DbTreeItem *DbTreeModel::findItem(QStandardItem* parentItem, DbTreeItem::Type type, Db* db) +{ + DbTreeItem* item = nullptr; + DbTreeItem* subItem = nullptr; + for (int i = 0; i < parentItem->rowCount(); i++) + { + item = dynamic_cast(parentItem->child(i)); + + // Search recursively + if (item->hasChildren()) + { + subItem = findItem(item, type, db); + if (subItem) + return subItem; + } + + if (item->getType() != type) + continue; + + if (item->text() != db->getName()) + continue; + + return item; + } + + return nullptr; +} + +QList DbTreeModel::findItems(QStandardItem* parentItem, DbTreeItem::Type type) +{ + QList items; + DbTreeItem* item = nullptr; + for (int i = 0; i < parentItem->rowCount(); i++) + { + item = dynamic_cast(parentItem->child(i)); + + // Search recursively + if (item->getType() == DbTreeItem::Type::DIR) + items += findItems(item, type); + + if (item->getType() != type) + continue; + + items += item; + } + + return items; +} + +QStandardItem* DbTreeModel::root() const +{ + return invisibleRootItem(); +} + +void DbTreeModel::loadDbList() +{ + clear(); + readGroups(DBLIST->getDbList()); +} + +void DbTreeModel::itemChangedVisibility(DbTreeItem* item) +{ + emit updateItemHidden(item); +} + +void DbTreeModel::setTreeView(DbTreeView *value) +{ + treeView = value; + connect(treeView, &QTreeView::expanded, this, &DbTreeModel::expanded); + connect(treeView, &QTreeView::collapsed, this, &DbTreeModel::collapsed); + connect(this, SIGNAL(updateItemHidden(DbTreeItem*)), treeView, SLOT(updateItemHidden(DbTreeItem*))); +} + +QStringList DbTreeModel::mimeTypes() const +{ + QStringList types = QStandardItemModel::mimeTypes(); + types << MIMETYPE; + return types; +} + +QMimeData *DbTreeModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *data = QStandardItemModel::mimeData(indexes); + if (!data) + return nullptr; + + if (indexes.size() == 0) + return nullptr; + + QByteArray output; + QDataStream stream(&output, QIODevice::WriteOnly); + + QList urlList; + QStringList textList; + + DbTreeItem* item = nullptr; + stream << reinterpret_cast(indexes.size()); + for (const QModelIndex& idx : indexes) + { + item = dynamic_cast(itemFromIndex(idx)); + stream << item->signature(); + + textList << item->text(); + if (item->getType() == DbTreeItem::Type::DB) + urlList << QUrl("file://"+item->getDb()->getPath()); + } + data->setData(MIMETYPE, output); + data->setText(textList.join("\n")); + data->setUrls(urlList); + + return data; +} + +bool DbTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + UNUSED(action); + // The result means: do we want the old item to be removed from the tree? + bool invokeStdAction = false; + bool res = pasteData(data, row, column, parent, Qt::IgnoreAction, &invokeStdAction); + if (!invokeStdAction) + return res; + + return QStandardItemModel::dropMimeData(data, action, row, column, parent); +} + +bool DbTreeModel::pasteData(const QMimeData* data, int row, int column, const QModelIndex& parent, Qt::DropAction defaultAction, bool* invokeStdAction) +{ + // The result means: do we want the old item to be removed from the tree? + DbTreeItem* dstItem = nullptr; + if (parent.isValid()) + { + QModelIndex idx = parent.child(row, column); + if (idx.isValid()) + dstItem = dynamic_cast(itemFromIndex(idx)); + else // drop on top of the parent + dstItem = dynamic_cast(itemFromIndex(parent)); + } + else + { + dstItem = dynamic_cast(item(row, column)); + } + + if (data->formats().contains(MIMETYPE)) + return dropDbTreeItem(getDragItems(data), dstItem, defaultAction, *invokeStdAction); + else if (data->hasUrls()) + return dropUrls(data->urls()); + else + return false; +} + +void DbTreeModel::interruptableStarted(Interruptable* obj) +{ + if (interruptables.size() == 0) + treeView->getDbTree()->showWidgetCover(); + + interruptables << obj; +} + +void DbTreeModel::interruptableFinished(Interruptable* obj) +{ + interruptables.removeOne(obj); + if (interruptables.size() == 0) + treeView->getDbTree()->hideWidgetCover(); +} + +QList DbTreeModel::getDragItems(const QMimeData* data) +{ + QList items; + QByteArray byteData = data->data(MIMETYPE); + QDataStream stream(&byteData, QIODevice::ReadOnly); + + qint32 itemCount; + stream >> itemCount; + + DbTreeItem* item = nullptr; + QString signature; + for (qint32 i = 0; i < itemCount; i++) + { + stream >> signature; + item = findItemBySignature(signature); + if (item) + items << item; + } + + return items; +} + +QList DbTreeModel::getItemsForIndexes(const QModelIndexList& indexes) const +{ + QList items; + for (const QModelIndex& idx : indexes) + { + if (idx.isValid()) + items << dynamic_cast(itemFromIndex(idx)); + } + + return items; +} + +void DbTreeModel::staticInit() +{ +} + +bool DbTreeModel::dropDbTreeItem(const QList& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction, bool& invokeStdDropAction) +{ + // The result means: do we want the old item to be removed from the tree? + if (srcItems.size() == 0) + return false; + + DbTreeItem* srcItem = srcItems.first(); + switch (srcItem->getType()) + { + case DbTreeItem::Type::TABLE: + case DbTreeItem::Type::VIEW: + { + if (!dstItem) + return false; + + if (srcItem->getDb() == dstItem->getDb()) + return true; + + return dropDbObjectItem(srcItems, dstItem, defaultAction); + } + case DbTreeItem::Type::DB: + case DbTreeItem::Type::DIR: + invokeStdDropAction = true; + break; + case DbTreeItem::Type::COLUMN: + case DbTreeItem::Type::TABLES: + case DbTreeItem::Type::INDEXES: + case DbTreeItem::Type::INDEX: + case DbTreeItem::Type::TRIGGERS: + case DbTreeItem::Type::TRIGGER: + case DbTreeItem::Type::VIEWS: + case DbTreeItem::Type::COLUMNS: + case DbTreeItem::Type::VIRTUAL_TABLE: + case DbTreeItem::Type::ITEM_PROTOTYPE: + break; + } + + return false; +} + +bool DbTreeModel::dropDbObjectItem(const QList& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction) +{ + bool copy = false; + bool move = false; + bool includeData = false; + bool includeIndexes = false; + bool includeTriggers = false; + + if (defaultAction == Qt::CopyAction) + { + copy = true; + includeData = true; + includeIndexes = true; + includeTriggers = true; + } + else if (defaultAction == Qt::MoveAction) + { + move = true; + includeData = true; + includeIndexes = true; + includeTriggers = true; + } + else + { + QMenu menu; + QAction* copyAction = menu.addAction(ICONS.ACT_COPY, tr("Copy")); + QAction* moveAction = menu.addAction(ICONS.ACT_CUT, tr("Move")); + menu.addSeparator(); + QCheckBox *includeDataCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include data")); + QCheckBox *includeIndexesCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include indexes")); + QCheckBox *includeTriggersCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include triggers")); + menu.addSeparator(); + menu.addAction(ICONS.ACT_ABORT, tr("Abort")); + + connect(moveAction, &QAction::triggered, [&move]() {move = true;}); + connect(copyAction, &QAction::triggered, [©]() {copy = true;}); + + menu.exec(treeView->mapToGlobal(treeView->getLastDropPosition())); + + includeData = includeDataCheck->isChecked(); + includeIndexes = includeIndexesCheck->isChecked(); + includeTriggers = includeTriggersCheck->isChecked(); + } + + // The result means: do we want the old item to be removed from the tree? + if (!copy && !move) + return false; + + moveOrCopyDbObjects(srcItems, dstItem, move, includeData, includeIndexes, includeTriggers); + return move; +} + +QCheckBox* DbTreeModel::createCopyOrMoveMenuCheckBox(QMenu* menu, const QString& label) +{ + QWidget* parentWidget = new QWidget(menu); + parentWidget->setLayout(new QVBoxLayout()); + QMargins margins = parentWidget->layout()->contentsMargins(); + parentWidget->layout()->setContentsMargins(margins.left(), 0, margins.right(), 0); + + QCheckBox *cb = new QCheckBox(label); + cb->setChecked(true); + parentWidget->layout()->addWidget(cb); + + QWidgetAction *action = new QWidgetAction(menu); + action->setDefaultWidget(parentWidget); + menu->addAction(action); + return cb; +} + +bool DbTreeModel::dropUrls(const QList& urls) +{ + for (const QUrl& url : urls) + { + if (!url.isLocalFile()) + { + qDebug() << url.toString() + "skipped, not a local file."; + continue; + } + + DbDialog dialog(DbDialog::ADD, MAINWINDOW); + dialog.setPath(url.toLocalFile()); + dialog.exec(); + } + return false; +} + +void DbTreeModel::moveOrCopyDbObjects(const QList& srcItems, DbTreeItem* dstItem, bool move, bool includeData, bool includeIndexes, bool includeTriggers) +{ + if (srcItems.size() == 0) + return; + + DbTreeItem* srcItem = srcItems.first(); + Db* srcDb = srcItem->getDb(); + Db* dstDb = dstItem->getDb(); + + QStringList srcNames; + for (DbTreeItem* item : srcItems) + srcNames << item->text(); + + interruptableStarted(dbOrganizer); + if (move) + dbOrganizer->moveObjectsToDb(srcDb, srcNames, dstDb, includeData, includeIndexes, includeTriggers); + else + dbOrganizer->copyObjectsToDb(srcDb, srcNames, dstDb, includeData, includeIndexes, includeTriggers); +} + +bool DbTreeModel::confirmReferencedTables(const QStringList& tables) +{ + QMessageBox::StandardButton result = QMessageBox::question(MAINWINDOW, tr("Referenced tables"), + tr("Do you want to include following referenced tables as well:\n%1").arg(tables.join(", "))); + + return result == QMessageBox::Yes; +} + +bool DbTreeModel::resolveNameConflict(QString& nameInConflict) +{ + bool ok = false; + QInputDialog tmpDialog; // just for a cancel button text + QString result = QInputDialog::getText(MAINWINDOW, tr("Name conflict"), + tr("Following object already exists in the target database.\nPlease enter new, unique name, or " + "press '%1' to abort the operation:").arg(tmpDialog.cancelButtonText()), + QLineEdit::Normal, nameInConflict, &ok); + + if (ok) + nameInConflict = result; + + return ok; +} + +bool DbTreeModel::confirmConversion(const QList >& diffs) +{ + VersionConvertSummaryDialog dialog(MAINWINDOW); + dialog.setWindowTitle(tr("SQL statements conversion")); + dialog.setSides(diffs); + return dialog.exec() == QDialog::Accepted; +} + +bool DbTreeModel::confirmConversionErrors(const QHash>& errors) +{ + ErrorsConfirmDialog dialog(MAINWINDOW); + dialog.setTopLabel(tr("Following error occurred while converting SQL statements to the target SQLite version:")); + dialog.setBottomLabel(tr("Would you like to ignore those errors and proceed?")); + dialog.setErrors(errors); + return dialog.exec() == QDialog::Accepted; +} +bool DbTreeModel::getIgnoreDbLoadedSignal() const +{ + return ignoreDbLoadedSignal; +} + +void DbTreeModel::setIgnoreDbLoadedSignal(bool value) +{ + ignoreDbLoadedSignal = value; +} + +bool DbTreeModel::hasDbTreeItem(const QMimeData *data) +{ + return data->formats().contains(MIMETYPE); +} + +void DbTreeModel::dbObjectsMoveFinished(bool success, Db* srcDb, Db* dstDb) +{ + if (!success) + { + interruptableFinished(dbOrganizer); + return; + } + + DBTREE->refreshSchema(srcDb); + DBTREE->refreshSchema(dstDb); + interruptableFinished(dbOrganizer); +} + +void DbTreeModel::dbObjectsCopyFinished(bool success, Db* srcDb, Db* dstDb) +{ + dbObjectsMoveFinished(success, srcDb, dstDb); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h new file mode 100644 index 0000000..c92fa2c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h @@ -0,0 +1,135 @@ +#ifndef DBTREEMODEL_H +#define DBTREEMODEL_H + +#include "db/db.h" +#include "dbtreeitem.h" +#include "services/config.h" +#include "guiSQLiteStudio_global.h" +#include "common/strhash.h" +#include +#include + +class DbManager; +class DbTreeView; +class DbPlugin; +class DbObjectOrganizer; +class QMenu; +class QCheckBox; + +class GUI_API_EXPORT DbTreeModel : public QStandardItemModel +{ + Q_OBJECT + + public: + DbTreeModel(); + ~DbTreeModel(); + + void connectDbManagerSignals(); + DbTreeItem* findItem(DbTreeItem::Type type, const QString &name); + DbTreeItem* findItem(DbTreeItem::Type type, Db* db); + DbTreeItem* findItemBySignature(const QString& signature); + QList findItems(DbTreeItem::Type type); + void move(QStandardItem* itemToMove, QStandardItem* newParentItem, int newRow = -1); + void move(QStandardItem* itemToMove, int newRow); + DbTreeItem *createGroup(const QString& name, QStandardItem *parent = nullptr); + void deleteGroup(QStandardItem* groupItem); + QStandardItem *root() const; + QStringList getGroupFor(QStandardItem* item); + void storeGroups(); + void refreshSchema(Db* db); + QList getAllItemsAsFlatList() const; + void setTreeView(DbTreeView *value); + QVariant data(const QModelIndex &index, int role) const; + QStringList mimeTypes() const; + QMimeData* mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + bool pasteData(const QMimeData* data, int row, int column, const QModelIndex& parent, Qt::DropAction defaultAction = Qt::IgnoreAction, + bool *invokeStdAction = nullptr); + void interruptableStarted(Interruptable* obj); + void interruptableFinished(Interruptable* obj); + bool getIgnoreDbLoadedSignal() const; + void setIgnoreDbLoadedSignal(bool value); + bool hasDbTreeItem(const QMimeData* data); + QList getDragItems(const QMimeData* data); + QList getItemsForIndexes(const QModelIndexList& indexes) const; + + static DbTreeItem* findItem(QStandardItem *parentItem, DbTreeItem::Type type, const QString &name); + static DbTreeItem* findItem(QStandardItem* parentItem, DbTreeItem::Type type, Db* db); + static QList findItems(QStandardItem* parentItem, DbTreeItem::Type type); + static void staticInit(); + + static const constexpr char* MIMETYPE = "application/x-sqlitestudio-dbtreeitem"; + + private: + void readGroups(QList dbList); + QList childsToConfig(QStandardItem* item); + void restoreGroup(const Config::DbGroupPtr& group, QList* dbList = nullptr, QStandardItem *parent = nullptr); + bool applyFilter(QStandardItem* parentItem, const QString& filter); + void refreshSchema(Db* db, QStandardItem* item); + void collectExpandedState(QHash& state, QStandardItem* parentItem = nullptr); + QStandardItem* refreshSchemaDb(Db* db); + QList refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort); + StrHash > refreshSchemaTableColumns(const StrHash& columns); + StrHash > refreshSchemaIndexes(const StrHash& indexes, bool sort); + StrHash > refreshSchemaTriggers(const StrHash& triggers, bool sort); + QList refreshSchemaViews(const QStringList &views, bool sort); + void populateChildItemsWithDb(QStandardItem* parentItem, Db* db); + void refreshSchemaBuild(QStandardItem* dbItem, QList tables, StrHash > indexes, + StrHash > triggers, QList views, StrHash > allTableColumns); + void restoreExpandedState(const QHash& expandedState, QStandardItem* parentItem); + QString getToolTip(DbTreeItem *item) const; + QString getDbToolTip(DbTreeItem *item) const; + QString getTableToolTip(DbTreeItem *item) const; + QList getChildsAsFlatList(QStandardItem* item) const; + bool dropDbTreeItem(const QList& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction, bool &invokeStdDropAction); + bool dropDbObjectItem(const QList& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction); + QCheckBox* createCopyOrMoveMenuCheckBox(QMenu* menu, const QString& label); + bool dropUrls(const QList& urls); + void moveOrCopyDbObjects(const QList& srcItems, DbTreeItem* dstItem, bool move, bool includeData, bool includeIndexes, bool includeTriggers); + + static bool confirmReferencedTables(const QStringList& tables); + static bool resolveNameConflict(QString& nameInConflict); + static bool confirmConversion(const QList>& diffs); + static bool confirmConversionErrors(const QHash >& errors); + + static const QString toolTipTableTmp; + static const QString toolTipHdrRowTmp; + static const QString toolTipRowTmp; + static const QString toolTipIconRowTmp; + + DbTreeView* treeView = nullptr; + bool requireSchemaReloading = false; + DbObjectOrganizer* dbOrganizer = nullptr; + QList interruptables; + bool ignoreDbLoadedSignal = false; + QString currentFilter; + + private slots: + void expanded(const QModelIndex &index); + void collapsed(const QModelIndex &index); + void dbAdded(Db* db); + void dbUpdated(const QString &oldName, Db* db); + void dbRemoved(Db* db); + void dbConnected(Db* db); + void dbDisconnected(Db* db); + void dbUnloaded(Db* db); + void dbLoaded(Db* db); + void massSaveBegins(); + void massSaveCommited(); + void markSchemaReloadingRequired(); + void dbObjectsMoveFinished(bool success, Db* srcDb, Db* dstDb); + void dbObjectsCopyFinished(bool success, Db* srcDb, Db* dstDb); + + public slots: + void loadDbList(); + void itemChangedVisibility(DbTreeItem* item); + void applyFilter(const QString& filter); + void dbRemoved(const QString& name); + void dbRemoved(QStandardItem* item); + void interrupt(); + + signals: + void updateItemHidden(DbTreeItem* item); +}; + +#endif // DBTREEMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp new file mode 100644 index 0000000..7785b8f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp @@ -0,0 +1,255 @@ +#include "dbtreeview.h" +#include "dbtreemodel.h" +#include "dbtreeitemdelegate.h" +#include "mainwindow.h" +#include "services/dbmanager.h" +#include "common/unused.h" +#include +#include +#include +#include +#include + +DbTreeView::DbTreeView(QWidget *parent) : + QTreeView(parent) +{ + contextMenu = new QMenu(this); + connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + + setHeaderHidden(true); + setContextMenuPolicy(Qt::CustomContextMenu); + setSelectionMode(QAbstractItemView::ExtendedSelection); + + itemDelegate = new DbTreeItemDelegate(); + setItemDelegate(itemDelegate); +} + +DbTreeView::~DbTreeView() +{ + delete contextMenu; + delete itemDelegate; +} + +void DbTreeView::setDbTree(DbTree *dbTree) +{ + this->dbTree = dbTree; +} + +DbTree* DbTreeView::getDbTree() const +{ + return dbTree; +} + +DbTreeItem *DbTreeView::currentItem() +{ + return dynamic_cast(model()->itemFromIndex(currentIndex())); +} + +DbTreeItem *DbTreeView::itemAt(const QPoint &pos) +{ + return dynamic_cast(model()->itemFromIndex(indexAt(pos))); +} + +QList DbTreeView::selectionItems() +{ + QList items; + QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); + foreach (QModelIndex modIdx, selectedIndexes) + items += dynamic_cast(model()->itemFromIndex(modIdx)); + + return items; +} + +DbTreeModel *DbTreeView::model() const +{ + return dynamic_cast(QTreeView::model()); +} + +void DbTreeView::showMenu(const QPoint &pos) +{ + contextMenu->clear(); + + DbTreeItem* itemUnderCursor = itemAt(pos); + if (!itemUnderCursor) + selectionModel()->clear(); + + DbTreeItem* item = getItemForAction(); + dbTree->setupActionsForMenu(item, contextMenu); + if (contextMenu->actions().size() == 0) + return; + + dbTree->updateActionStates(item); + contextMenu->popup(mapToGlobal(pos)); +} + +void DbTreeView::updateItemHidden(DbTreeItem* item) +{ + setRowHidden(item->index().row(), item->index().parent(), item->isHidden()); +} + +DbTreeItem *DbTreeView::getItemForAction(bool onlySelected) const +{ + QModelIndex idx = selectionModel()->currentIndex(); + if (onlySelected && !selectionModel()->isSelected(idx)) + return nullptr; + + return dynamic_cast(model()->itemFromIndex(idx)); +} + +void DbTreeView::dragEnterEvent(QDragEnterEvent* e) +{ + QTreeView::dragEnterEvent(e); + if (e->mimeData()->hasUrls()) + e->acceptProposedAction(); +} + +void DbTreeView::dragMoveEvent(QDragMoveEvent *event) +{ + QTreeView::dragMoveEvent(event); + + DbTreeItem* dstItem = itemAt(event->pos()); + + // Depending on where we drop we need a type of item we drop ON, + // or type of parent item if we drop ABOVE/BELOW. If we drop on empty space, + // we leave type as default. + if (dstItem) + { + QAbstractItemView::DropIndicatorPosition dropPosition = dropIndicatorPosition(); + switch (dropPosition) + { + case QAbstractItemView::OnItem: + break; + case QAbstractItemView::AboveItem: + case QAbstractItemView::BelowItem: + { + dstItem = dstItem->parentDbTreeItem(); + break; + } + case QAbstractItemView::OnViewport: + dstItem = nullptr; + break; + } + } + + //qDebug() << event->mimeData()->formats(); + const QMimeData* data = event->mimeData(); + if (dbTree->isMimeDataValidForItem(data, dstItem)) + event->acceptProposedAction(); + else + event->ignore(); +} + +void DbTreeView::mouseDoubleClickEvent(QMouseEvent *event) +{ + DbTreeItem* itemUnderCursor = itemAt(event->pos()); + if (itemUnderCursor && !handleDoubleClick(itemUnderCursor)) + return; + + QTreeView::mouseDoubleClickEvent(event); +} + +bool DbTreeView::handleDoubleClick(DbTreeItem *item) +{ + switch (item->getType()) + { + case DbTreeItem::Type::DIR: + break; + case DbTreeItem::Type::DB: + { + if (item->getDb()->isValid()) + return handleDbDoubleClick(item); + } + case DbTreeItem::Type::TABLES: + break; + case DbTreeItem::Type::VIRTUAL_TABLE: + // TODO if module for virtual table is loaded - show virtual table window + break; + case DbTreeItem::Type::TABLE: + return handleTableDoubleClick(item); + case DbTreeItem::Type::INDEXES: + break; + case DbTreeItem::Type::INDEX: + return handleIndexDoubleClick(item); + case DbTreeItem::Type::TRIGGERS: + break; + case DbTreeItem::Type::TRIGGER: + return handleTriggerDoubleClick(item); + case DbTreeItem::Type::VIEWS: + break; + case DbTreeItem::Type::VIEW: + return handleViewDoubleClick(item); + case DbTreeItem::Type::COLUMNS: + break; + case DbTreeItem::Type::COLUMN: + return handleColumnDoubleClick(item); + case DbTreeItem::Type::ITEM_PROTOTYPE: + break; + } + + return true; +} + +bool DbTreeView::handleDbDoubleClick(DbTreeItem *item) +{ + if (!item->getDb()->isOpen()) + { + dbTree->getAction(DbTree::CONNECT_TO_DB)->trigger(); + return false; + } + return true; +} + +bool DbTreeView::handleTableDoubleClick(DbTreeItem *item) +{ + dbTree->openTable(item); + return false; +} + +bool DbTreeView::handleIndexDoubleClick(DbTreeItem *item) +{ + dbTree->editIndex(item); + return false; +} + +bool DbTreeView::handleTriggerDoubleClick(DbTreeItem *item) +{ + dbTree->editTrigger(item); + return false; +} + +bool DbTreeView::handleViewDoubleClick(DbTreeItem *item) +{ + dbTree->openView(item); + return false; +} + +bool DbTreeView::handleColumnDoubleClick(DbTreeItem *item) +{ + dbTree->editColumn(item); + return false; +} + +QPoint DbTreeView::getLastDropPosition() const +{ + return lastDropPosition; +} + +QModelIndexList DbTreeView::getSelectedIndexes() const +{ + QModelIndexList idxList = selectedIndexes(); + if (currentIndex().isValid() && !idxList.contains(currentIndex())) + idxList << currentIndex(); + + return idxList; +} + +void DbTreeView::dropEvent(QDropEvent* e) +{ + lastDropPosition = e->pos(); + QTreeView::dropEvent(e); + if (!e->isAccepted() && e->mimeData()->hasUrls() && !dbTree->getModel()->hasDbTreeItem(e->mimeData())) + { + dbTree->getModel()->dropMimeData(e->mimeData(), Qt::CopyAction, -1, -1, dbTree->getModel()->root()->index()); + e->accept(); + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h new file mode 100644 index 0000000..3ec33b4 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h @@ -0,0 +1,57 @@ +#ifndef DBTREEVIEW_H +#define DBTREEVIEW_H + +#include "dbtree.h" +#include "guiSQLiteStudio_global.h" +#include +#include +#include + +class QMenu; +class QStandardItemModel; +class DbTreeItemDelegate; + +class GUI_API_EXPORT DbTreeView : public QTreeView +{ + Q_OBJECT + public: + explicit DbTreeView(QWidget *parent = 0); + ~DbTreeView(); + + void setDbTree(DbTree* dbTree); + DbTree* getDbTree() const; + + DbTreeItem *currentItem(); + DbTreeItem *itemAt(const QPoint& pos); + QList selectionItems(); + DbTreeModel *model() const; + DbTreeItem *getItemForAction(bool onlySelected = false) const; + QPoint getLastDropPosition() const; + QModelIndexList getSelectedIndexes() const; + + protected: + void dragEnterEvent(QDragEnterEvent* e); + void dragMoveEvent(QDragMoveEvent *event); + void mouseDoubleClickEvent(QMouseEvent* event); + void dropEvent(QDropEvent*e); + + private: + bool handleDoubleClick(DbTreeItem* item); + bool handleDbDoubleClick(DbTreeItem* item); + bool handleTableDoubleClick(DbTreeItem* item); + bool handleIndexDoubleClick(DbTreeItem* item); + bool handleTriggerDoubleClick(DbTreeItem* item); + bool handleViewDoubleClick(DbTreeItem* item); + bool handleColumnDoubleClick(DbTreeItem* item); + + QMenu* contextMenu = nullptr; + DbTree* dbTree = nullptr; + DbTreeItemDelegate* itemDelegate = nullptr; + QPoint lastDropPosition; + + private slots: + void showMenu(const QPoint &pos); + void updateItemHidden(DbTreeItem* item); +}; + +#endif // DBTREEVIEW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp b/SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp new file mode 100644 index 0000000..033eb1c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/debugconsole.cpp @@ -0,0 +1,79 @@ +#include "debugconsole.h" +#include "ui_debugconsole.h" +#include "iconmanager.h" +#include + +DebugConsole::DebugConsole(QWidget *parent) : + QDialog(parent), + ui(new Ui::DebugConsole) +{ + ui->setupUi(this); + ui->textEdit->setReadOnly(true); + + QPushButton* resetBtn = ui->buttonBox->button(QDialogButtonBox::Reset); + connect(resetBtn, SIGNAL(clicked()), this, SLOT(reset())); + + initFormats(); +} + +DebugConsole::~DebugConsole() +{ + delete ui; +} + +void DebugConsole::debug(const QString &msg) +{ + message(msg, dbgFormat); +} + +void DebugConsole::warning(const QString &msg) +{ + message(msg, wrnFormat); +} + +void DebugConsole::critical(const QString &msg) +{ + message(msg, criFormat); +} + +void DebugConsole::fatal(const QString &msg) +{ + message(msg, fatFormat); +} + +void DebugConsole::initFormats() +{ + dbgFormat.setForeground(Qt::blue); + wrnFormat.setForeground(Qt::darkRed); + criFormat.setForeground(Qt::red); + criFormat.setFontUnderline(true); + fatFormat.setForeground(Qt::red); + fatFormat.setFontUnderline(true); + + QFontMetrics fm(ui->textEdit->font()); + int indent = fm.width(QString("X").repeated(25)); + ui->textEdit->document()->setIndentWidth(indent); + + blockFormat.setIndent(1); + blockFormat.setTextIndent(-indent); +} + +void DebugConsole::message(const QString &msg, const QTextCharFormat &format) +{ + ui->textEdit->setCurrentCharFormat(format); + QTextCursor cur = ui->textEdit->textCursor(); + + cur.insertText(msg); + cur.mergeBlockFormat(blockFormat); + cur.insertBlock(blockFormat); +} + +void DebugConsole::reset() +{ + ui->textEdit->clear(); +} + +void DebugConsole::showEvent(QShowEvent*) +{ + setWindowIcon(ICONS.SQLITESTUDIO_APP); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/debugconsole.h b/SQLiteStudio3/guiSQLiteStudio/debugconsole.h new file mode 100644 index 0000000..e13525c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/debugconsole.h @@ -0,0 +1,44 @@ +#ifndef DEBUGCONSOLE_H +#define DEBUGCONSOLE_H + +#include "guiSQLiteStudio_global.h" +#include +#include + +namespace Ui { +class DebugConsole; +} + +class GUI_API_EXPORT DebugConsole : public QDialog +{ + Q_OBJECT + + public: + explicit DebugConsole(QWidget *parent = 0); + ~DebugConsole(); + + protected: + void showEvent(QShowEvent*); + + private: + void initFormats(); + void message(const QString& msg, const QTextCharFormat& format); + + Ui::DebugConsole *ui = nullptr; + QTextCharFormat dbgFormat; + QTextCharFormat wrnFormat; + QTextCharFormat criFormat; + QTextCharFormat fatFormat; + QTextBlockFormat blockFormat; + + private slots: + void reset(); + + public slots: + void debug(const QString& msg); + void warning(const QString& msg); + void critical(const QString& msg); + void fatal(const QString& msg); +}; + +#endif // DEBUGCONSOLE_H diff --git a/SQLiteStudio3/guiSQLiteStudio/debugconsole.ui b/SQLiteStudio3/guiSQLiteStudio/debugconsole.ui new file mode 100644 index 0000000..f24e233 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/debugconsole.ui @@ -0,0 +1,73 @@ + + + DebugConsole + + + + 0 + 0 + 745 + 344 + + + + SQLiteStudio Debug Console + + + + + + + Courier + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Reset + + + + + + + + + buttonBox + accepted() + DebugConsole + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DebugConsole + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp new file mode 100644 index 0000000..df790de --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp @@ -0,0 +1,94 @@ +#include "aboutdialog.h" +#include "ui_aboutdialog.h" +#include "common/utils.h" +#include "sqlitestudio.h" +#include "iconmanager.h" +#include "services/extralicensemanager.h" +#include +#include + +AboutDialog::AboutDialog(InitialMode initialMode, QWidget *parent) : + QDialog(parent), + ui(new Ui::AboutDialog) +{ + init(initialMode); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} + +void AboutDialog::init(InitialMode initialMode) +{ + ui->setupUi(this); + ui->leftIcon->setPixmap(ICONS.SQLITESTUDIO_APP.toQIcon().pixmap(200, 200)); + + ui->tabWidget->setCurrentWidget(initialMode == ABOUT ? ui->about : ui->license); + + QString distName; + switch (getDistributionType()) + { + case DistributionType::PORTABLE: + distName = tr("Portable distribution."); + break; + case DistributionType::OSX_BOUNDLE: + distName = tr("MacOS X application boundle distribution."); + break; + case DistributionType::OS_MANAGED: + distName = tr("Operating system managed distribution."); + break; + } + + QString newLabelValue = ui->aboutLabel->text().arg(SQLITESTUDIO->getVersionString(), distName); + ui->aboutLabel->setText(newLabelValue); + + licenseContents = ""; + int row = 1; + + QHash licenses = SQLITESTUDIO->getExtraLicenseManager()->getLicenses(); + QHashIterator it(licenses); + while (it.hasNext()) + { + it.next(); + readLicense(row++, it.key(), it.value()); + } + + buildIndex(); + + ui->licenseEdit->setHtml(licenseContents); + indexContents.clear(); + licenseContents.clear(); +} + +void AboutDialog::buildIndex() +{ + static const QString entryTpl = QStringLiteral("
  • %1
  • "); + QStringList entries; + for (const QString& idx : indexContents) + entries += entryTpl.arg(idx); + + licenseContents.prepend("

    Table of contents:

      " + entries.join("") + "
    "); +} + +void AboutDialog::readLicense(int row, const QString& title, const QString& path) +{ + QString rowNum = QString::number(row); + QString contents = readFile(path); + licenseContents += "

    " + rowNum + ". " + title + "

    "; + licenseContents += "
    " + contents + "
    "; + indexContents += title; +} + +QString AboutDialog::readFile(const QString& path) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + { + qCritical() << "Error opening" << file.fileName(); + return QString::null; + } + QString contents = QString::fromLatin1(file.readAll()).toHtmlEscaped(); + file.close(); + return contents; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h new file mode 100644 index 0000000..3c828c0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h @@ -0,0 +1,37 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include +#include + +namespace Ui { + class AboutDialog; +} + +class GUI_API_EXPORT AboutDialog : public QDialog +{ + Q_OBJECT + + public: + enum InitialMode + { + ABOUT, + LICENSES + }; + + AboutDialog(InitialMode initialMode, QWidget *parent = 0); + ~AboutDialog(); + + private: + void init(InitialMode initialMode); + void buildIndex(); + void readLicense(int row, const QString& title, const QString& path); + QString readFile(const QString& path); + + Ui::AboutDialog *ui = nullptr; + QStringList indexContents; + QString licenseContents; +}; + +#endif // ABOUTDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui new file mode 100644 index 0000000..67fa632 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui @@ -0,0 +1,109 @@ + + + AboutDialog + + + + 0 + 0 + 741 + 447 + + + + About SQLiteStudio and licenses + + + + + + 1 + + + + About + + + + + + <html><head/><body><p align="center"><span style=" font-size:11pt; font-weight:600;">SQLiteStudio v%1</span></p><p align="center">Free, open-source, cross-platform SQLite database manager.<br/><a href="http://sqlitestudio.pl"><span style=" text-decoration: underline; color:#0000ff;">http://sqlitestudio.pl</span></a><br/></p><p align="center">%2<br/></p><p align="center">Author and active maintainer:<br/>SalSoft (<a href="http://salsoft.com.pl"><span style=" text-decoration: underline; color:#0000ff;">http://salsoft.com.pl</span></a>)<br/></p></body></html> + + + true + + + + + + + + Licenses + + + + + + true + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + AboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp new file mode 100644 index 0000000..8f5d433 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp @@ -0,0 +1,220 @@ +#include "bugdialog.h" +#include "ui_bugdialog.h" +#include "iconmanager.h" +#include "uiutils.h" +#include "common/utils.h" +#include "sqlitestudio.h" +#include "mainwindow.h" +#include "bugreportlogindialog.h" +#include "services/pluginmanager.h" +#include "services/bugreporter.h" +#include "services/notifymanager.h" +#include +#include +#include + +BugDialog::BugDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::BugDialog) +{ + init(); +} + +BugDialog::~BugDialog() +{ + delete ui; +} + +void BugDialog::setFeatureRequestMode(bool feature) +{ + bugMode = !feature; + updateState(); +} + +void BugDialog::init() +{ + ui->setupUi(this); + resize(width(), height() - 50); + + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Send")); + + connect(ui->moreDetailsGroup, SIGNAL(toggled(bool)), this, SLOT(updateState())); + connect(ui->shortDescriptionEdit, SIGNAL(textChanged(QString)), this, SLOT(validate())); + connect(ui->longDescriptionEdit, SIGNAL(textChanged()), this, SLOT(validate())); + connect(ui->emailEdit, SIGNAL(textChanged(QString)), this, SLOT(validate())); + connect(ui->helpButton, SIGNAL(clicked()), this, SLOT(help())); + connect(ui->loginButton, SIGNAL(clicked()), this, SLOT(logIn())); + + ui->versionEdit->setText(SQLITESTUDIO->getVersionString()); + ui->osEdit->setText(getOsString()); + ui->pluginsEdit->setText(PLUGINS->getLoadedPluginNames().join(", ")); + + user = CFG_CORE.Internal.BugReportUser.get(); + + if (CFG_CORE.Internal.BugReportRecentError.get()) + { + ui->shortDescriptionEdit->setText(CFG_CORE.Internal.BugReportRecentTitle.get()); + ui->longDescriptionEdit->setPlainText(CFG_CORE.Internal.BugReportRecentContents.get()); + } + + updateState(); + validate(); +} + +QString BugDialog::getMessageAboutReportHistory() +{ + return tr("You can see all your reported bugs and ideas by selecting menu '%1' and then '%2'.").arg(MAINWINDOW->getSQLiteStudioMenu()->title()) + .arg(MAINWINDOW->getAction(MainWindow::BUG_REPORT_HISTORY)->text()); + return ""; +} + +void BugDialog::finishedBugReport(bool success, const QString& errorMsg) +{ + if (success) + { + notifyInfo(tr("A bug report sent successfully.") + " " + getMessageAboutReportHistory()); + } + else + { + CFG_CORE.Internal.BugReportRecentError.set(true); + notifyError(tr("An error occurred while sending a bug report: %1\n%2").arg(errorMsg, + tr("You can retry sending. The contents will be restored when you open a report dialog after an error like this."))); + } +} + +void BugDialog::finishedFeatureRequest(bool success, const QString& errorMsg) +{ + if (success) + { + notifyInfo(tr("An idea proposal sent successfully.") + " " + getMessageAboutReportHistory()); + } + else + { + CFG_CORE.Internal.BugReportRecentError.set(true); + notifyError(tr("An error occurred while sending an idea proposal: %1\n%2").arg(errorMsg, + tr("You can retry sending. The contents will be restored when you open a report dialog after an error like this."))); + } +} + +void BugDialog::updateState() +{ + ui->scrollArea->setVisible(ui->moreDetailsGroup->isChecked()); + + ui->moreDetailsGroup->setVisible(bugMode); + if (bugMode) + { + setWindowTitle(tr("A bug report")); + ui->shortDescriptionEdit->setPlaceholderText(tr("Describe problem in few words")); + ui->longDescriptionEdit->setPlaceholderText(tr("Describe problem and how to reproduce it")); + } + else + { + setWindowTitle(tr("A new feature idea")); + ui->shortDescriptionEdit->setPlaceholderText(tr("A title for your idea")); + ui->longDescriptionEdit->setPlaceholderText(tr("Describe your idea in more details")); + } + + if (user.isNull()) + { + ui->currentLoginLabel->setToolTip(tr("Reporting as an unregistered user, using e-mail address.")); + ui->currentLoginLabel->setPixmap(ICONS.USER_UNKNOWN); + ui->emailEdit->setEnabled(true); + ui->emailEdit->clear(); + ui->loginButton->setText(tr("Log in")); + ui->loginButton->setIcon(ICONS.USER); + } + else + { + ui->currentLoginLabel->setToolTip(tr("Reporting as a registered user.")); + ui->currentLoginLabel->setPixmap(ICONS.USER); + ui->emailEdit->setText(user); + ui->emailEdit->setEnabled(false); + ui->loginButton->setText(tr("Log out")); + ui->loginButton->setIcon(ICONS.USER_UNKNOWN); + } +} + +void BugDialog::validate() +{ + bool emailOk = !user.isNull() || validateEmail(ui->emailEdit->text()); + int shortSize = ui->shortDescriptionEdit->text().trimmed().size(); + int longSize = ui->longDescriptionEdit->toPlainText().trimmed().size(); + bool shortOk = shortSize >= 10 && shortSize <= 100; + bool longOk = longSize >= 30; + + setValidStateWihtTooltip(ui->emailEdit, tr("Providing true email address will make it possible to contact you regarding your report. " + "To learn more, press 'help' button on the right side."), + emailOk, tr("Enter vaild e-mail address, or log in.")); + + setValidState(ui->shortDescriptionEdit, shortOk, tr("Short description requires at least 10 characters, but not more than 100. " + "Longer description can be entered in the field below.")); + + setValidState(ui->longDescriptionEdit, longOk, tr("Long description requires at least 30 characters.")); + + bool valid = shortOk && longOk && emailOk; + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); +} + +void BugDialog::help() +{ + if (user.isNull()) + QDesktopServices::openUrl(QUrl(BUGS->getReporterEmailHelpUrl())); + else + QDesktopServices::openUrl(QUrl(BUGS->getReporterUserAndPasswordHelpUrl())); +} + +void BugDialog::logIn() +{ + if (!user.isNull()) + { + // Log out + user = QString(); + updateState(); + BUGS->clearBugReportCredentials(); + return; + } + + BugReportLoginDialog dialog(this); + if (dialog.exec() != QDialog::Accepted) + return; + + if (!dialog.isValid()) + return; + + BUGS->useBugReportCredentials(dialog.getLogin(), dialog.getPassword()); + user = dialog.getLogin(); + updateState(); +} + +void BugDialog::accept() +{ + CFG_CORE.Internal.BugReportRecentError.set(false); + CFG_CORE.Internal.BugReportRecentTitle.set(ui->shortDescriptionEdit->text()); + CFG_CORE.Internal.BugReportRecentContents.set(ui->longDescriptionEdit->toPlainText()); + + if (bugMode) + { + if (user.isNull()) + { + BUGS->reportBug(ui->emailEdit->text(), ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), ui->versionEdit->text(), + ui->osEdit->text(), ui->pluginsEdit->text(), BugDialog::finishedBugReport); + } + else + { + BUGS->reportBug(ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), ui->versionEdit->text(), ui->osEdit->text(), ui->pluginsEdit->text(), + BugDialog::finishedFeatureRequest); + } + } + else + { + if (user.isNull()) + { + BUGS->requestFeature(ui->emailEdit->text(), ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), BugDialog::finishedFeatureRequest); + } + else + { + BUGS->requestFeature(ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), BugDialog::finishedFeatureRequest); + } + } + QDialog::accept(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h new file mode 100644 index 0000000..bf60104 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h @@ -0,0 +1,42 @@ +#ifndef BUGDIALOG_H +#define BUGDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class BugDialog; +} + +class GUI_API_EXPORT BugDialog : public QDialog +{ + Q_OBJECT + + public: + explicit BugDialog(QWidget *parent = 0); + ~BugDialog(); + + void setFeatureRequestMode(bool feature); + + private: + void init(); + + static QString getMessageAboutReportHistory(); + static void finishedBugReport(bool success, const QString& errorMsg); + static void finishedFeatureRequest(bool success, const QString& errorMsg); + + Ui::BugDialog *ui = nullptr; + bool bugMode = true; + QString user; + + private slots: + void updateState(); + void validate(); + void help(); + void logIn(); + + public slots: + void accept(); +}; + +#endif // BUGDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui new file mode 100644 index 0000000..f2dbcf3 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui @@ -0,0 +1,208 @@ + + + BugDialog + + + + 0 + 0 + 516 + 421 + + + + Dialog + + + + + + Reporter + + + + + + + + + :/icons/img/user_unknown.png + + + + + + + E-mail address + + + + + + + Log in + + + + :/icons/img/user.png:/icons/img/user.png + + + + + + + ... + + + + :/icons/img/help.png:/icons/img/help.png + + + + + + + + + + Short description + + + + + + + + + + + + Detailed description + + + + + + + + + + + + Show more details + + + true + + + false + + + + + + true + + + + + 0 + 0 + 462 + 209 + + + + + + + SQLiteStudio version + + + + + + + + + + + + Operating system + + + + + + + + + + + + Loaded plugins + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + BugDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + BugDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp new file mode 100644 index 0000000..19727fe --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp @@ -0,0 +1,94 @@ +#include "bugreportlogindialog.h" +#include "ui_bugreportlogindialog.h" +#include "uiutils.h" +#include "services/bugreporter.h" +#include "iconmanager.h" +#include "common/widgetcover.h" +#include + +BugReportLoginDialog::BugReportLoginDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::BugReportLoginDialog) +{ + init(); +} + +BugReportLoginDialog::~BugReportLoginDialog() +{ + delete ui; +} + +bool BugReportLoginDialog::isValid() const +{ + return validCredentials; +} + +QString BugReportLoginDialog::getLogin() const +{ + return ui->loginEdit->text(); +} + +QString BugReportLoginDialog::getPassword() const +{ + return ui->passwordEdit->text(); +} + +void BugReportLoginDialog::init() +{ + ui->setupUi(this); + connect(ui->loginEdit, SIGNAL(textChanged(QString)), this, SLOT(credentialsChanged())); + connect(ui->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(credentialsChanged())); + connect(ui->validationButton, SIGNAL(clicked()), this, SLOT(remoteValidation())); + connect(BUGS, SIGNAL(credentialsValidationResult(bool,QString)), this, SLOT(remoteValidationResult(bool,QString))); + + widgetCover = new WidgetCover(this); + widgetCover->initWithInterruptContainer(tr("Abort")); + connect(widgetCover, SIGNAL(cancelClicked()), this, SLOT(abortRemoteValidation())); + + validate(); +} + +void BugReportLoginDialog::credentialsChanged() +{ + validCredentials = false; + validate(); +} + +void BugReportLoginDialog::validate() +{ + QString login = ui->loginEdit->text(); + QString pass = ui->passwordEdit->text(); + + bool loginOk = login.size() >= 2; + bool passOk = pass.size() >= 5; + + setValidState(ui->loginEdit, loginOk, tr("A login must be at least 2 characters long.")); + setValidState(ui->passwordEdit, passOk, tr("A password must be at least 5 characters long.")); + + bool credentialsOk = loginOk && passOk; + ui->validationButton->setEnabled(credentialsOk); + ui->validationLabel->setEnabled(credentialsOk); + + bool valid = credentialsOk && validCredentials; + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); +} + +void BugReportLoginDialog::abortRemoteValidation() +{ + BUGS->abortCredentialsValidation(); +} + +void BugReportLoginDialog::remoteValidation() +{ + widgetCover->show(); + BUGS->validateBugReportCredentials(ui->loginEdit->text(), ui->passwordEdit->text()); +} + +void BugReportLoginDialog::remoteValidationResult(bool success, const QString& errorMessage) +{ + validCredentials = success; + ui->validationButton->setIcon(success ? ICONS.TEST_CONN_OK : ICONS.TEST_CONN_ERROR); + ui->validationLabel->setText(success ? tr("Valid") : errorMessage); + validate(); + widgetCover->hide(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h new file mode 100644 index 0000000..131ba3d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h @@ -0,0 +1,40 @@ +#ifndef BUGREPORTLOGINDIALOG_H +#define BUGREPORTLOGINDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class BugReportLoginDialog; +} + +class WidgetCover; + +class GUI_API_EXPORT BugReportLoginDialog : public QDialog +{ + Q_OBJECT + + public: + explicit BugReportLoginDialog(QWidget *parent = 0); + ~BugReportLoginDialog(); + + bool isValid() const; + QString getLogin() const; + QString getPassword() const; + + private: + void init(); + + Ui::BugReportLoginDialog *ui = nullptr; + bool validCredentials = false; + WidgetCover* widgetCover = nullptr; + + private slots: + void credentialsChanged(); + void validate(); + void abortRemoteValidation(); + void remoteValidation(); + void remoteValidationResult(bool success, const QString& errorMessage); +}; + +#endif // BUGREPORTLOGINDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui new file mode 100644 index 0000000..f6597bc --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui @@ -0,0 +1,132 @@ + + + BugReportLoginDialog + + + + 0 + 0 + 343 + 197 + + + + Log in + + + + + + Credentials + + + + + + Login: + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + + + + + + + + Validation + + + + + + Validate + + + + :/icons/img/test_conn_error.png:/icons/img/test_conn_error.png + + + Qt::ToolButtonTextBesideIcon + + + + + + + Validation result message + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + BugReportLoginDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + BugReportLoginDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp new file mode 100644 index 0000000..f4e48fe --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp @@ -0,0 +1,616 @@ +#include "columndialog.h" +#include "common/unused.h" +#include "ui_columndialog.h" +#include "columndialogconstraintsmodel.h" +#include "iconmanager.h" +#include "newconstraintdialog.h" +#include "dialogs/constraintdialog.h" +#include "constraints/constraintpanel.h" +#include "datatype.h" +#include "uiutils.h" +#include +#include +#include +#include +#include + +ColumnDialog::ColumnDialog(Db* db, QWidget *parent) : + QDialog(parent), + ui(new Ui::ColumnDialog), + db(db) +{ + init(); +} + +ColumnDialog::~ColumnDialog() +{ + delete ui; +} + +void ColumnDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + setWindowIcon(ICONS.COLUMN); + + ui->scale->setStrict(true); + ui->precision->setStrict(true); + + ui->typeCombo->addItem(""); + foreach (DataType::Enum type, DataType::getAllTypes()) + ui->typeCombo->addItem(DataType::toString(type)); + + connect(ui->typeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateDataType())); + + constraintsModel = new ColumnDialogConstraintsModel(); + ui->constraintsView->setModel(constraintsModel); + initActions(); + + setupConstraintCheckBoxes(); + + connect(ui->advancedCheck, SIGNAL(toggled(bool)), this, SLOT(switchMode(bool))); + + connect(ui->constraintsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateConstraintsToolbarState())); + connect(ui->constraintsView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editConstraint(QModelIndex))); + connect(constraintsModel, SIGNAL(constraintsChanged()), this, SLOT(updateConstraints())); + connect(constraintsModel, SIGNAL(constraintsChanged()), this, SLOT(updateState())); + + connect(ui->pkButton, SIGNAL(clicked()), this, SLOT(configurePk())); + connect(ui->fkButton, SIGNAL(clicked()), this, SLOT(configureFk())); + connect(ui->checkButton, SIGNAL(clicked()), this, SLOT(configureCheck())); + connect(ui->defaultButton, SIGNAL(clicked()), this, SLOT(configureDefault())); + connect(ui->notNullButton, SIGNAL(clicked()), this, SLOT(configureNotNull())); + connect(ui->collateButton, SIGNAL(clicked()), this, SLOT(configureCollate())); + connect(ui->uniqueButton, SIGNAL(clicked()), this, SLOT(configureUnique())); + + updateState(); +} + +void ColumnDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void ColumnDialog::createActions() +{ + createAction(ADD_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_ADD, tr("Add constraint", "column dialog"), this, SLOT(addConstraint()), ui->constraintsToolbar); + createAction(EDIT_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_EDIT, tr("Edit constraint", "column dialog"), this, SLOT(editConstraint()), ui->constraintsToolbar); + createAction(DEL_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_DEL, tr("Delete constraint", "column dialog"), this, SLOT(delConstraint()), ui->constraintsToolbar); + createAction(MOVE_CONSTRAINT_UP, ICONS.MOVE_UP, tr("Move constraint up", "column dialog"), this, SLOT(moveConstraintUp()), ui->constraintsToolbar); + createAction(MOVE_CONSTRAINT_DOWN, ICONS.MOVE_DOWN, tr("Move constraint down", "column dialog"), this, SLOT(moveConstraintDown()), ui->constraintsToolbar); + ui->constraintsToolbar->addSeparator(); + createAction(ADD_PK, ICONS.CONSTRAINT_PRIMARY_KEY_ADD, tr("Add a primary key", "column dialog"), this, SLOT(addPk()), ui->constraintsToolbar); + createAction(ADD_FK, ICONS.CONSTRAINT_FOREIGN_KEY_ADD, tr("Add a foreign key", "column dialog"), this, SLOT(addFk()), ui->constraintsToolbar); + createAction(ADD_UNIQUE, ICONS.CONSTRAINT_UNIQUE_ADD, tr("Add an unique constraint", "column dialog"), this, SLOT(addUnique()), ui->constraintsToolbar); + createAction(ADD_CHECK, ICONS.CONSTRAINT_CHECK_ADD, tr("Add a check constraint", "column dialog"), this, SLOT(addCheck()), ui->constraintsToolbar); + createAction(ADD_NOT_NULL, ICONS.CONSTRAINT_NOT_NULL_ADD, tr("Add a not null constraint", "column dialog"), this, SLOT(addNotNull()), ui->constraintsToolbar); + createAction(ADD_COLLATE, ICONS.CONSTRAINT_COLLATION_ADD, tr("Add a collate constraint", "column dialog"), this, SLOT(addCollate()), ui->constraintsToolbar); + createAction(ADD_DEFAULT, ICONS.CONSTRAINT_DEFAULT_ADD, tr("Add a default constraint", "column dialog"), this, SLOT(addDefault()), ui->constraintsToolbar); +} + +void ColumnDialog::setupDefShortcuts() +{ +} + +void ColumnDialog::updateConstraintsToolbarState() +{ + QModelIndex idx = ui->constraintsView->selectionModel()->currentIndex(); + bool hasSelected = idx.isValid(); + bool isFirst = false; + bool isLast = false; + if (constraintsModel->rowCount() > 0) + { + isFirst = (idx.row() == 0); + isLast = (idx.row() == (constraintsModel->rowCount() - 1)); + } + + actionMap[EDIT_CONSTRAINT]->setEnabled(hasSelected); + actionMap[DEL_CONSTRAINT]->setEnabled(hasSelected); + actionMap[MOVE_CONSTRAINT_UP]->setEnabled(hasSelected && !isFirst); + actionMap[MOVE_CONSTRAINT_DOWN]->setEnabled(hasSelected && !isLast); +} + +void ColumnDialog::updateState() +{ + ui->pkButton->setEnabled(ui->pkCheck->isChecked()); + ui->fkButton->setEnabled(ui->fkCheck->isChecked()); + ui->uniqueButton->setEnabled(ui->uniqueCheck->isChecked()); + ui->notNullButton->setEnabled(ui->notNullCheck->isChecked()); + ui->checkButton->setEnabled(ui->checkCheck->isChecked()); + ui->collateButton->setEnabled(ui->collateCheck->isChecked()); + ui->defaultButton->setEnabled(ui->defaultCheck->isChecked()); + updateConstraintsToolbarState(); +} + +void ColumnDialog::addConstraint(ConstraintDialog::Constraint mode) +{ + NewConstraintDialog dialog(mode, column.data(), db, this); + if (dialog.exec() != QDialog::Accepted) + return; + + SqliteStatement* constrStmt = dialog.getConstraint(); + SqliteCreateTable::Column::Constraint* constr = dynamic_cast(constrStmt); + if (!constr) + { + qCritical() << "Constraint returned from ConstraintDialog was not of column type, while we're trying to add column constraint."; + return; + } + + constraintsModel->appendConstraint(constr); + ui->constraintsView->resizeColumnToContents(0); + ui->constraintsView->resizeColumnToContents(1); +} + +void ColumnDialog::setupConstraintCheckBoxes() +{ + ui->pkCheck->setIcon(ICONS.CONSTRAINT_PRIMARY_KEY); + ui->fkCheck->setIcon(ICONS.CONSTRAINT_FOREIGN_KEY); + ui->uniqueCheck->setIcon(ICONS.CONSTRAINT_UNIQUE); + ui->notNullCheck->setIcon(ICONS.CONSTRAINT_NOT_NULL); + ui->checkCheck->setIcon(ICONS.CONSTRAINT_CHECK); + ui->collateCheck->setIcon(ICONS.CONSTRAINT_COLLATION); + ui->defaultCheck->setIcon(ICONS.CONSTRAINT_DEFAULT); + + connect(ui->pkCheck, SIGNAL(clicked(bool)), this, SLOT(pkToggled(bool))); + connect(ui->fkCheck, SIGNAL(clicked(bool)), this, SLOT(fkToggled(bool))); + connect(ui->uniqueCheck, SIGNAL(clicked(bool)), this, SLOT(uniqueToggled(bool))); + connect(ui->notNullCheck, SIGNAL(clicked(bool)), this, SLOT(notNullToggled(bool))); + connect(ui->checkCheck, SIGNAL(clicked(bool)), this, SLOT(checkToggled(bool))); + connect(ui->collateCheck, SIGNAL(clicked(bool)), this, SLOT(collateToggled(bool))); + connect(ui->defaultCheck, SIGNAL(clicked(bool)), this, SLOT(defaultToggled(bool))); + + for (QCheckBox* cb : { + ui->pkCheck, + ui->fkCheck, + ui->uniqueCheck, + ui->notNullCheck, + ui->checkCheck, + ui->collateCheck, + ui->defaultCheck + }) + { + connect(cb, SIGNAL(toggled(bool)), this, SLOT(updateState())); + } +} + +void ColumnDialog::addConstraint() +{ + addConstraint(ConstraintDialog::UNKNOWN); +} + +void ColumnDialog::editConstraint() +{ + QModelIndex idx = ui->constraintsView->currentIndex(); + editConstraint(idx); +} + +void ColumnDialog::delConstraint() +{ + QModelIndex idx = ui->constraintsView->currentIndex(); + delConstraint(idx); +} + +void ColumnDialog::editConstraint(const QModelIndex& idx) +{ + if (!idx.isValid()) + return; + + SqliteCreateTable::Column::Constraint* constr = constraintsModel->getConstraint(idx.row()); + editConstraint(constr); +} + +void ColumnDialog::editConstraint(SqliteCreateTable::Column::Constraint* constraint) +{ + ConstraintDialog dialog(ConstraintDialog::EDIT, constraint, column.data(), db, this); + if (dialog.exec() != QDialog::Accepted) + return; + + ui->constraintsView->resizeColumnToContents(0); + ui->constraintsView->resizeColumnToContents(1); + updateConstraints(); +} + +void ColumnDialog::delConstraint(const QModelIndex& idx) +{ + if (!idx.isValid()) + return; + + SqliteCreateTable::Column::Constraint* constr = constraintsModel->getConstraint(idx.row()); + + QString arg = constr->name.isNull() ? constr->typeString() : constr->name; + QString msg = tr("Are you sure you want to delete constraint '%1'?", "column dialog").arg(arg); + int btn = QMessageBox::question(this, tr("Delete constraint", "column dialog"), msg); + if (btn != QMessageBox::Yes) + return; + + constraintsModel->delConstraint(idx.row()); +} + +void ColumnDialog::configureConstraint(SqliteCreateTable::Column::Constraint::Type type) +{ + SqliteCreateTable::Column::Constraint* constraint = column->getConstraint(type); + if (!constraint) + { + qCritical() << "Called ColumnDialog::configureConstraint(), but there's no specified type constraint in the column!"; + return; + } + editConstraint(constraint); +} + +void ColumnDialog::addEmptyConstraint(SqliteCreateTable::Column::Constraint::Type type) +{ + SqliteCreateTable::Column::Constraint* constr = new SqliteCreateTable::Column::Constraint(); + constr->type = type; + constraintsModel->appendConstraint(constr); + constr->rebuildTokens(); +} + +void ColumnDialog::delAllConstraint(SqliteCreateTable::Column::Constraint::Type type) +{ + SqliteCreateTable::Column::Constraint* constr = nullptr; + while ((constr = column->getConstraint(type)) != nullptr) + constraintsModel->delConstraint(constr); +} + +void ColumnDialog::constraintToggled(SqliteCreateTable::Column::Constraint::Type type, bool enabled) +{ + if (enabled) + addEmptyConstraint(type); + else + delAllConstraint(type); +} + +void ColumnDialog::updateConstraintState(SqliteCreateTable::Column::Constraint* constraint) +{ + QToolButton* toolButton = getToolButtonForConstraint(constraint); + if (!toolButton) + return; + + bool result = true; + ConstraintPanel* panel = ConstraintPanel::produce(constraint); + if (!panel) + { + qCritical() << "Could not produce ConstraintPanel for constraint validation in ColumnDialog::updateConstraintState()."; + } + else + { + panel->setDb(db); + panel->setConstraint(constraint); + result = panel->validateOnly(); + delete panel; + } + + QString errMsg = tr("Correct the constraint's configuration."); + if (db->getDialect() == Dialect::Sqlite2 && isUnofficialSqlite2Constraint(constraint)) + { + QString tooltip = tr("This constraint is not officially supported by SQLite 2,\nbut it's okay to use it."); + setValidStateWihtTooltip(toolButton, tooltip, result, errMsg); + } + else + { + setValidState(toolButton, result, errMsg); + } + + if (!result) + { + QPushButton* btn = ui->buttonBox->button(QDialogButtonBox::Ok); + btn->setEnabled(false); + } +} + +QCheckBox* ColumnDialog::getCheckBoxForConstraint(SqliteCreateTable::Column::Constraint* constraint) +{ + switch (constraint->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return ui->pkCheck; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return ui->notNullCheck; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return ui->uniqueCheck; + case SqliteCreateTable::Column::Constraint::CHECK: + return ui->checkCheck; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return ui->defaultCheck; + case SqliteCreateTable::Column::Constraint::COLLATE: + return ui->collateCheck; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return ui->fkCheck; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return nullptr; +} + +QToolButton* ColumnDialog::getToolButtonForConstraint(SqliteCreateTable::Column::Constraint* constraint) +{ + switch (constraint->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return ui->pkButton; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return ui->notNullButton; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return ui->uniqueButton; + case SqliteCreateTable::Column::Constraint::CHECK: + return ui->checkButton; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return ui->defaultButton; + case SqliteCreateTable::Column::Constraint::COLLATE: + return ui->collateButton; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return ui->fkButton; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return nullptr; +} + +bool ColumnDialog::isUnofficialSqlite2Constraint(SqliteCreateTable::Column::Constraint* constraint) +{ + switch (constraint->type) + { + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + case SqliteCreateTable::Column::Constraint::COLLATE: + return true; + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + case SqliteCreateTable::Column::Constraint::NOT_NULL: + case SqliteCreateTable::Column::Constraint::UNIQUE: + case SqliteCreateTable::Column::Constraint::CHECK: + case SqliteCreateTable::Column::Constraint::DEFAULT: + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return false; +} + +void ColumnDialog::moveConstraintUp() +{ + QModelIndex idx = ui->constraintsView->currentIndex(); + if (!idx.isValid()) + return; + + constraintsModel->moveConstraintUp(idx.row()); +} + +void ColumnDialog::moveConstraintDown() +{ + QModelIndex idx = ui->constraintsView->currentIndex(); + if (!idx.isValid()) + return; + + constraintsModel->moveConstraintDown(idx.row()); +} + +void ColumnDialog::addPk() +{ + addConstraint(ConstraintDialog::PK); +} + +void ColumnDialog::addFk() +{ + addConstraint(ConstraintDialog::FK); +} + +void ColumnDialog::addUnique() +{ + addConstraint(ConstraintDialog::UNIQUE); +} + +void ColumnDialog::addCheck() +{ + addConstraint(ConstraintDialog::CHECK); +} + +void ColumnDialog::addCollate() +{ + addConstraint(ConstraintDialog::COLLATE); +} + +void ColumnDialog::addNotNull() +{ + addConstraint(ConstraintDialog::NOTNULL); +} + +void ColumnDialog::addDefault() +{ + addConstraint(ConstraintDialog::DEFAULT); +} + +void ColumnDialog::configurePk() +{ + configureConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY); +} + +void ColumnDialog::configureFk() +{ + configureConstraint(SqliteCreateTable::Column::Constraint::FOREIGN_KEY); +} + +void ColumnDialog::configureUnique() +{ + configureConstraint(SqliteCreateTable::Column::Constraint::UNIQUE); +} + +void ColumnDialog::configureCheck() +{ + configureConstraint(SqliteCreateTable::Column::Constraint::CHECK); +} + +void ColumnDialog::configureCollate() +{ + configureConstraint(SqliteCreateTable::Column::Constraint::COLLATE); +} + +void ColumnDialog::configureNotNull() +{ + configureConstraint(SqliteCreateTable::Column::Constraint::NOT_NULL); +} + +void ColumnDialog::configureDefault() +{ + configureConstraint(SqliteCreateTable::Column::Constraint::DEFAULT); +} + +void ColumnDialog::pkToggled(bool enabled) +{ + constraintToggled(SqliteCreateTable::Column::Constraint::PRIMARY_KEY, enabled); +} + +void ColumnDialog::fkToggled(bool enabled) +{ + constraintToggled(SqliteCreateTable::Column::Constraint::FOREIGN_KEY, enabled); +} + +void ColumnDialog::uniqueToggled(bool enabled) +{ + constraintToggled(SqliteCreateTable::Column::Constraint::UNIQUE, enabled); +} + +void ColumnDialog::checkToggled(bool enabled) +{ + constraintToggled(SqliteCreateTable::Column::Constraint::CHECK, enabled); +} + +void ColumnDialog::collateToggled(bool enabled) +{ + constraintToggled(SqliteCreateTable::Column::Constraint::COLLATE, enabled); +} + +void ColumnDialog::notNullToggled(bool enabled) +{ + constraintToggled(SqliteCreateTable::Column::Constraint::NOT_NULL, enabled); +} + +void ColumnDialog::defaultToggled(bool enabled) +{ + constraintToggled(SqliteCreateTable::Column::Constraint::DEFAULT, enabled); +} + +void ColumnDialog::switchMode(bool advanced) +{ + ui->constraintModesWidget->setCurrentWidget(advanced ? ui->advancedPage : ui->simplePage); +} + +void ColumnDialog::updateConstraints() +{ + QPushButton* btn = ui->buttonBox->button(QDialogButtonBox::Ok); + btn->setEnabled(true); + + for (QCheckBox* cb : { + ui->pkCheck, + ui->fkCheck, + ui->uniqueCheck, + ui->notNullCheck, + ui->checkCheck, + ui->collateCheck, + ui->defaultCheck + }) + { + cb->setChecked(false); + } + + for (QToolButton* tb : { + ui->pkButton, + ui->fkButton, + ui->uniqueButton, + ui->notNullButton, + ui->checkButton, + ui->collateButton, + ui->defaultButton + }) + { + setValidState(tb, true); + } + + foreach (SqliteCreateTable::Column::Constraint* constr, column->constraints) + updateConstraint(constr); +} + +void ColumnDialog::updateConstraint(SqliteCreateTable::Column::Constraint* constraint) +{ + QCheckBox* checkBox = getCheckBoxForConstraint(constraint); + if (checkBox) + { + checkBox->setChecked(true); + updateConstraintState(constraint); + } +} + +void ColumnDialog::setColumn(SqliteCreateTable::Column* value) +{ + column = SqliteCreateTable::ColumnPtr::create(*value); + column->setParent(value->parent()); + constraintsModel->setColumn(column.data()); + + ui->name->setText(value->name); + if (value->type) + { + ui->typeCombo->setEditText(value->type->name); + ui->scale->setValue(value->type->scale, false); + ui->precision->setValue(value->type->precision, false); + } + + updateConstraints(); +} + +SqliteCreateTable::Column* ColumnDialog::getModifiedColumn() +{ + column->name = ui->name->text(); + updateDataType(); + column->rebuildTokens(); + + return new SqliteCreateTable::Column(*column); +} + +QToolBar* ColumnDialog::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return nullptr; +} + +void ColumnDialog::updateDataType() +{ + if (!column) + return; + + QString typeTxt = ui->typeCombo->currentText(); + QString scaleTxt = ui->scale->getValue().toString(); + QString precisionTxt = ui->precision->getValue().toString(); + if (!typeTxt.isEmpty()) + { + if (!column->type) + { + column->type = new SqliteColumnType(); + column->type->setParent(column.data()); + } + + column->type->name = typeTxt; + + if (!scaleTxt.isEmpty()) + column->type->scale = ui->scale->getValue(); + + if (!precisionTxt.isEmpty()) + column->type->precision = ui->precision->getValue(); + + column->type->rebuildTokens(); + } + else if (column->type) // there was a type, but there's not now + { + delete column->type; + column->type = nullptr; + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h new file mode 100644 index 0000000..c567e1a --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h @@ -0,0 +1,113 @@ +#ifndef COLUMNDIALOG_H +#define COLUMNDIALOG_H + +#include "parser/ast/sqlitecreatetable.h" +#include "common/extactioncontainer.h" +#include "constraintdialog.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class ColumnDialogConstraintsModel; +class QCheckBox; +class QToolButton; + +namespace Ui { + class ColumnDialog; +} + +class GUI_API_EXPORT ColumnDialog : public QDialog, public ExtActionContainer +{ + Q_OBJECT + + public: + enum Action + { + ADD_CONSTRAINT, + EDIT_CONSTRAINT, + DEL_CONSTRAINT, + MOVE_CONSTRAINT_UP, + MOVE_CONSTRAINT_DOWN, + ADD_PK, + ADD_FK, + ADD_UNIQUE, + ADD_CHECK, + ADD_DEFAULT, + ADD_NOT_NULL, + ADD_COLLATE + }; + + enum ToolBar + { + }; + + explicit ColumnDialog(Db* db, QWidget *parent = 0); + ~ColumnDialog(); + + void init(); + void setColumn(SqliteCreateTable::Column* value); + SqliteCreateTable::Column* getModifiedColumn(); + QToolBar* getToolBar(int toolbar) const; + + protected: + void changeEvent(QEvent *e); + void createActions(); + void setupDefShortcuts(); + + private: + void addConstraint(ConstraintDialog::Constraint mode); + void setupConstraintCheckBoxes(); + void editConstraint(SqliteCreateTable::Column::Constraint* constraint); + void delConstraint(const QModelIndex& idx); + void configureConstraint(SqliteCreateTable::Column::Constraint::Type type); + void addEmptyConstraint(SqliteCreateTable::Column::Constraint::Type type); + void delAllConstraint(SqliteCreateTable::Column::Constraint::Type type); + void constraintToggled(SqliteCreateTable::Column::Constraint::Type type, bool enabled); + void updateConstraintState(SqliteCreateTable::Column::Constraint* constraint); + QCheckBox* getCheckBoxForConstraint(SqliteCreateTable::Column::Constraint* constraint); + QToolButton* getToolButtonForConstraint(SqliteCreateTable::Column::Constraint* constraint); + bool isUnofficialSqlite2Constraint(SqliteCreateTable::Column::Constraint* constraint); + + Ui::ColumnDialog *ui = nullptr; + SqliteCreateTable::ColumnPtr column; + ColumnDialogConstraintsModel* constraintsModel = nullptr; + QCheckBox* modeCheckBox = nullptr; + Db* db = nullptr; + + private slots: + void updateConstraintsToolbarState(); + void updateState(); + void addConstraint(); + void editConstraint(); + void editConstraint(const QModelIndex& idx); + void delConstraint(); + void moveConstraintUp(); + void moveConstraintDown(); + void addPk(); + void addFk(); + void addUnique(); + void addCheck(); + void addCollate(); + void addNotNull(); + void addDefault(); + void configurePk(); + void configureFk(); + void configureUnique(); + void configureCheck(); + void configureCollate(); + void configureNotNull(); + void configureDefault(); + void pkToggled(bool enabled); + void fkToggled(bool enabled); + void uniqueToggled(bool enabled); + void checkToggled(bool enabled); + void collateToggled(bool enabled); + void notNullToggled(bool enabled); + void defaultToggled(bool enabled); + void switchMode(bool advanced); + void updateConstraints(); + void updateConstraint(SqliteCreateTable::Column::Constraint* constraint); + void updateDataType(); +}; + +#endif // COLUMNDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui new file mode 100644 index 0000000..ac7e5ae --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui @@ -0,0 +1,348 @@ + + + ColumnDialog + + + + 0 + 0 + 424 + 360 + + + + Column + + + true + + + + + + Name and type + + + + + + + 50 + 0 + + + + + + + + , + + + + + + + + 50 + 0 + + + + + + + + Data type: + + + + + + + Column name: + + + + + + + + + + Size: + + + + + + + + 120 + 0 + + + + true + + + + + + + + + + Constraints + + + + + + 0 + + + + + + + Unique + + + + + + + Configure + + + + + + + Foreign Key + + + + + + + Configure + + + + + + + Collate + + + + + + + Not NULL + + + + + + + Check condition + + + + + + + Primary Key + + + + + + + Default + + + + + + + Configure + + + + + + + Configure + + + + + + + Configure + + + + + + + Configure + + + + + + + Configure + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Advanced mode + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + NumericSpinBox + QSpinBox +
    common/numericspinbox.h
    +
    +
    + + name + typeCombo + scale + precision + pkCheck + pkButton + fkCheck + fkButton + uniqueCheck + uniqueButton + checkCheck + checkButton + notNullCheck + notNullButton + collateCheck + collateButton + defaultCheck + defaultButton + advancedCheck + buttonBox + constraintsView + + + + + buttonBox + accepted() + ColumnDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ColumnDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp new file mode 100644 index 0000000..853b680 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp @@ -0,0 +1,335 @@ +#include "columndialogconstraintsmodel.h" +#include "common/unused.h" +#include "iconmanager.h" + +ColumnDialogConstraintsModel::ColumnDialogConstraintsModel(QObject *parent) : + QAbstractTableModel(parent) +{ +} + +void ColumnDialogConstraintsModel::setColumn(SqliteCreateTable::Column* value) +{ + beginResetModel(); + column = value; + endResetModel(); +} + +SqliteCreateTable::Column::Constraint* ColumnDialogConstraintsModel::getConstraint(int constrIdx) const +{ + if (column.isNull()) + return nullptr; + + return column->constraints[constrIdx]; +} + +void ColumnDialogConstraintsModel::replaceConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr) +{ + if (column.isNull()) + return; + + delete column->constraints[constrIdx]; + column->constraints[constrIdx] = constr; + constr->setParent(column); + + emit constraintsChanged(); +} + +void ColumnDialogConstraintsModel::insertConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr) +{ + if (column.isNull()) + return; + + beginInsertRows(QModelIndex(), constrIdx, constrIdx); + column->constraints.insert(constrIdx, constr); + constr->setParent(column); + endInsertRows(); + + emit constraintsChanged(); +} + +void ColumnDialogConstraintsModel::appendConstraint(SqliteCreateTable::Column::Constraint* constr) +{ + if (column.isNull()) + return; + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + column->constraints.append(constr); + constr->setParent(column); + endInsertRows(); + + emit constraintsChanged(); +} + +void ColumnDialogConstraintsModel::delConstraint(int constrIdx) +{ + if (column.isNull()) + return; + + beginRemoveRows(QModelIndex(), constrIdx, constrIdx); + delete column->constraints[constrIdx]; + column->constraints.removeAt(constrIdx); + endRemoveRows(); + + emit constraintsChanged(); +} + +void ColumnDialogConstraintsModel::delConstraint(SqliteCreateTable::Column::Constraint* constr) +{ + if (column.isNull()) + return; + + int constrIdx = column->constraints.indexOf(constr); + if (constrIdx < -1) + return; + + delConstraint(constrIdx); +} + +void ColumnDialogConstraintsModel::moveConstraintUp(int constrIdx) +{ + moveConstraintColumnTo(constrIdx, constrIdx-1); +} + +void ColumnDialogConstraintsModel::moveConstraintDown(int constrIdx) +{ + moveConstraintColumnTo(constrIdx, constrIdx+1); +} + +void ColumnDialogConstraintsModel::moveConstraintColumnTo(int constrIdx, int newIdx) +{ + if (column.isNull()) + return; + + if (newIdx == constrIdx) + return; + + if (newIdx == constrIdx+1) + { + // See TableStructureModel::moveColumnTo() for details above code below. + int tmpIdx = newIdx; + newIdx = constrIdx; + constrIdx = tmpIdx; + } + + beginMoveRows(QModelIndex(), constrIdx, constrIdx, QModelIndex(), newIdx); + if (newIdx >= column->constraints.size()) + { + SqliteCreateTable::Column::Constraint* constr = column->constraints.takeAt(constrIdx); + column->constraints.append(constr); + } + else + column->constraints.move(constrIdx, newIdx); + + endMoveRows(); + + emit constraintsChanged(); +} + +ColumnDialogConstraintsModel::Column ColumnDialogConstraintsModel::getColumn(int colIdx) const +{ + return static_cast(colIdx); +} + +QIcon ColumnDialogConstraintsModel::getIcon(int rowIdx) const +{ + SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx]; + switch (constr->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return ICONS.CONSTRAINT_PRIMARY_KEY; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return ICONS.CONSTRAINT_NOT_NULL; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return ICONS.CONSTRAINT_UNIQUE; + case SqliteCreateTable::Column::Constraint::CHECK: + return ICONS.CONSTRAINT_CHECK; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return ICONS.CONSTRAINT_DEFAULT; + case SqliteCreateTable::Column::Constraint::COLLATE: + return ICONS.CONSTRAINT_COLLATION; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return ICONS.CONSTRAINT_FOREIGN_KEY; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return QIcon(); +} + +QString ColumnDialogConstraintsModel::getName(int rowIdx) const +{ + SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx]; + return constr->name; +} + +QString ColumnDialogConstraintsModel::getType(int rowIdx) const +{ + SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx]; + switch (constr->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return "PRIMARY KEY"; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return "NOT NULL"; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return "UNIQUE"; + case SqliteCreateTable::Column::Constraint::CHECK: + return "CHECK"; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return "DEFAULT"; + case SqliteCreateTable::Column::Constraint::COLLATE: + return "COLLATE"; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return "FOREIGN KEY"; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return QString::null; +} + +QString ColumnDialogConstraintsModel::getDetails(int rowIdx) const +{ + SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx]; + switch (constr->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return getPkDetails(constr); + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return getNotNullDetails(constr); + case SqliteCreateTable::Column::Constraint::UNIQUE: + return getUniqueDetails(constr); + case SqliteCreateTable::Column::Constraint::CHECK: + return getCheckDetails(constr); + case SqliteCreateTable::Column::Constraint::DEFAULT: + return getDefaultDetails(constr); + case SqliteCreateTable::Column::Constraint::COLLATE: + return getCollateDetails(constr); + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return getFkDetails(constr); + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return QString::null; +} + +QString ColumnDialogConstraintsModel::getPkDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive); + return getConstrDetails(constr, idx); +} + +QString ColumnDialogConstraintsModel::getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "NULL", Qt::CaseInsensitive); + return getConstrDetails(constr, idx); +} + +QString ColumnDialogConstraintsModel::getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive); + return getConstrDetails(constr, idx); +} + +QString ColumnDialogConstraintsModel::getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive); + return getConstrDetails(constr, idx); +} + +QString ColumnDialogConstraintsModel::getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "DEFAULT", Qt::CaseInsensitive); + return getConstrDetails(constr, idx); +} + +QString ColumnDialogConstraintsModel::getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "COLLATE", Qt::CaseInsensitive); + return getConstrDetails(constr, idx); +} + +QString ColumnDialogConstraintsModel::getFkDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive); + return getConstrDetails(constr, idx); +} + +QString ColumnDialogConstraintsModel::getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const +{ + TokenList tokens = constr->tokens.mid(tokenOffset + 1); + tokens.trimLeft(); + return tokens.detokenize(); +} + +int ColumnDialogConstraintsModel::rowCount(const QModelIndex& parent) const +{ + UNUSED(parent); + if (column.isNull()) + return 0; + + return column->constraints.size(); +} + +int ColumnDialogConstraintsModel::columnCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return 3; +} + +QVariant ColumnDialogConstraintsModel::data(const QModelIndex& index, int role) const +{ + if (column.isNull()) + return QVariant(); + + switch (getColumn(index.column())) + { + case Column::TYPE: + { + if (role == Qt::DecorationRole) + return getIcon(index.row()); + + if (role == Qt::DisplayRole) + return getType(index.row()); + + break; + } + case Column::NAME: + { + if (role == Qt::DisplayRole) + return getName(index.row()); + + break; + } + case Column::DETAILS: + if (role == Qt::DisplayRole) + return getDetails(index.row()); + + break; + } + return QVariant(); +} + +QVariant ColumnDialogConstraintsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QAbstractTableModel::headerData(section, orientation, role); + + if (orientation == Qt::Vertical) + return section + 1; + + switch (getColumn(section)) + { + case Column::TYPE: + return tr("Type", "column dialog constraints"); + case Column::NAME: + return tr("Name", "column dialog constraints"); + case Column::DETAILS: + return tr("Details", "column dialog constraints"); + } + return QVariant(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h new file mode 100644 index 0000000..f37933a --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h @@ -0,0 +1,58 @@ +#ifndef COLUMNDIALOGCONSTRAINTSMODEL_H +#define COLUMNDIALOGCONSTRAINTSMODEL_H + +#include "parser/ast/sqlitecreatetable.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class GUI_API_EXPORT ColumnDialogConstraintsModel : public QAbstractTableModel +{ + Q_OBJECT + public: + explicit ColumnDialogConstraintsModel(QObject *parent = 0); + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex& parent) const; + QVariant data(const QModelIndex& index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + void setColumn(SqliteCreateTable::Column* value); + SqliteCreateTable::Column::Constraint* getConstraint(int constrIdx) const; + void replaceConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr); + void insertConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr); + void appendConstraint(SqliteCreateTable::Column::Constraint* constr); + void delConstraint(int constrIdx); + void delConstraint(SqliteCreateTable::Column::Constraint* constr); + void moveConstraintUp(int constrIdx); + void moveConstraintDown(int constrIdx); + void moveConstraintColumnTo(int constrIdx, int newIdx); + + private: + enum class Column + { + TYPE, + NAME, + DETAILS + }; + + Column getColumn(int colIdx) const; + QIcon getIcon(int rowIdx) const; + QString getName(int rowIdx) const; + QString getType(int rowIdx) const; + QString getDetails(int rowIdx) const; + QString getPkDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getFkDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const; + + QPointer column; + + signals: + void constraintsChanged(); +}; + +#endif // COLUMNDIALOGCONSTRAINTSMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp new file mode 100644 index 0000000..932036e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp @@ -0,0 +1,1529 @@ +#include "configdialog.h" +#include "ui_configdialog.h" +#include "services/config.h" +#include "uiconfig.h" +#include "customconfigwidgetplugin.h" +#include "services/pluginmanager.h" +#include "formmanager.h" +#include "services/codeformatter.h" +#include "plugins/codeformatterplugin.h" +#include "configwidgets/styleconfigwidget.h" +#include "configwidgets/combodatawidget.h" +#include "configwidgets/listtostringlisthash.h" +#include "iconmanager.h" +#include "common/userinputfilter.h" +#include "multieditor/multieditorwidget.h" +#include "multieditor/multieditorwidgetplugin.h" +#include "plugins/confignotifiableplugin.h" +#include "mainwindow.h" +#include "common/unused.h" +#include "sqlitestudio.h" +#include "configmapper.h" +#include "datatype.h" +#include "uiutils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GET_FILTER_STRING(Widget, WidgetType, Method) \ + if (qobject_cast(Widget))\ + return qobject_cast(Widget)->Method() + " " + Widget->toolTip();\ + +#define GET_FILTER_STRING2(Widget, WidgetType) \ + WidgetType* w##WidgetType = qobject_cast(widget);\ + if (w##WidgetType)\ + return getFilterString(w##WidgetType) + " " + Widget->toolTip(); + +ConfigDialog::ConfigDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ConfigDialog) +{ + init(); +} + +ConfigDialog::~ConfigDialog() +{ + // Cancel transaction on CfgMain objects from plugins + rollbackPluginConfigs(); + + // Notify plugins about dialog being closed + UiConfiguredPlugin* cfgPlugin = nullptr; + foreach (Plugin* plugin, PLUGINS->getLoadedPlugins()) + { + cfgPlugin = dynamic_cast(plugin); + if (!cfgPlugin) + continue; + + cfgPlugin->configDialogClosed(); + } + + // Delete UI and other resources + delete ui; + safe_delete(configMapper); + + for (ConfigMapper* mapper : pluginConfigMappers.values()) + delete mapper; + + pluginConfigMappers.clear(); + +} + +void ConfigDialog::configureDataEditors(const QString& dataTypeString) +{ + ui->categoriesWidget->setVisible(false); + ui->stackedWidget->setCurrentWidget(ui->dataEditorsPage); + + for (int i = 0; i < ui->dataEditorsTypesList->count(); i++) + { + if (ui->dataEditorsTypesList->item(i)->text() == dataTypeString.toUpper()) + { + ui->dataEditorsTypesList->setCurrentRow(i); + return; + } + } + + addDataType(dataTypeString.toUpper()); +} + +QString ConfigDialog::getFilterString(QWidget *widget) +{ + // Common code for widgets with single method call + GET_FILTER_STRING(widget, QLabel, text); + GET_FILTER_STRING(widget, QAbstractButton, text); + GET_FILTER_STRING(widget, QLineEdit, text); + GET_FILTER_STRING(widget, QTextEdit, toPlainText); + GET_FILTER_STRING(widget, QPlainTextEdit, toPlainText); + GET_FILTER_STRING(widget, QGroupBox, title); + GET_FILTER_STRING(widget, QKeySequenceEdit, keySequence().toString); + + // Widgets needs a little more than single method call + GET_FILTER_STRING2(widget, QComboBox); + GET_FILTER_STRING2(widget, QTreeWidget); + GET_FILTER_STRING2(widget, QListWidget); + GET_FILTER_STRING2(widget, QTableWidget); + + return QString::null; +} + +QString ConfigDialog::getFilterString(QComboBox *widget) +{ + QStringList items; + for (int i = 0; i < widget->count(); i++) + items << widget->itemText(i); + + return items.join(" "); +} + +QString ConfigDialog::getFilterString(QTreeWidget *widget) +{ + QList items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); + QStringList strList; + foreach (QTreeWidgetItem* item, items) + for (int i = 0; i < widget->columnCount(); i++) + strList << item->text(i) + " " + item->toolTip(0); + + return strList.join(" "); +} + +QString ConfigDialog::getFilterString(QListWidget *widget) +{ + QList items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); + QStringList strList; + foreach (QListWidgetItem* item, items) + strList << item->text() + " " + item->toolTip(); + + return strList.join(" "); +} + +QString ConfigDialog::getFilterString(QTableWidget *widget) +{ + QList items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); + QStringList strList; + foreach (QTableWidgetItem* item, items) + strList << item->text() + " " + item->toolTip(); + + return strList.join(" "); +} + +void ConfigDialog::init() +{ + ui->setupUi(this); + setWindowIcon(ICONS.CONFIGURE); + + ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(0)); + + configMapper = new ConfigMapper(CfgMain::getPersistableInstances()); + connect(configMapper, SIGNAL(modified()), this, SLOT(markModified())); + connect(configMapper, &ConfigMapper::notifyEnabledWidgetModified, [=](QWidget* widget, CfgEntry* key, const QVariant& value) + { + UNUSED(widget); + for (ConfigNotifiablePlugin* plugin : notifiablePlugins) + plugin->configModified(key, value); + }); + + ui->categoriesFilter->setClearButtonEnabled(true); + UserInputFilter* filter = new UserInputFilter(ui->categoriesFilter, this, SLOT(applyFilter(QString))); + filter->setDelay(500); + + ui->stackedWidget->setCurrentWidget(ui->generalPage); + initPageMap(); + initInternalCustomConfigWidgets(); + initPlugins(); + initPluginsPage(); + initFormatterPlugins(); + initDataEditors(); + initShortcuts(); + + connect(ui->categoriesTree, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(switchPage(QTreeWidgetItem*))); + connect(ui->previewTabs, SIGNAL(currentChanged(int)), this, SLOT(updateStylePreview())); + connect(ui->activeStyleCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateStylePreview())); + connect(ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply())); + connect(ui->hideBuiltInPluginsCheck, SIGNAL(toggled(bool)), this, SLOT(updateBuiltInPluginsVisibility())); + + ui->activeStyleCombo->addItems(QStyleFactory::keys()); + + connect(ui->stackedWidget, SIGNAL(currentChanged(int)), this, SLOT(pageSwitched())); + + ui->hideBuiltInPluginsCheck->setChecked(true); + +#ifdef NO_AUTO_UPDATES + ui->updatesGroup->setVisible(false); +#endif + + load(); + updateStylePreview(); +} + +void ConfigDialog::load() +{ + updatingDataEditorItem = true; + configMapper->loadToWidget(ui->stackedWidget); + updatingDataEditorItem = false; + setModified(false); +} + +void ConfigDialog::save() +{ + MainWindow::getInstance()->setStyle(ui->activeStyleCombo->currentText()); + + QString loadedPlugins = collectLoadedPlugins(); + storeSelectedFormatters(); + CFG->beginMassSave(); + CFG_CORE.General.LoadedPlugins.set(loadedPlugins); + configMapper->saveFromWidget(ui->stackedWidget, true); + commitPluginConfigs(); + CFG->commitMassSave(); +} + +void ConfigDialog::storeSelectedFormatters() +{ + CodeFormatterPlugin* plugin = nullptr; + QTreeWidgetItem* item = nullptr; + QComboBox* combo = nullptr; + QString lang; + QString pluginName; + for (int i = 0, total = ui->formatterPluginsTree->topLevelItemCount(); i < total; ++i) + { + item = ui->formatterPluginsTree->topLevelItem(i); + lang = item->text(0); + + combo = formatterLangToPluginComboMap[lang]; + if (!combo) + { + qCritical() << "Could not find combo for lang " << lang << " in storeSelectedFormatters()"; + continue; + } + + pluginName = combo->currentData().toString(); + plugin = dynamic_cast(PLUGINS->getLoadedPlugin(pluginName)); + if (!plugin) + { + qCritical() << "Could not find plugin for lang " << lang << " in storeSelectedFormatters()"; + continue; + } + + FORMATTER->setFormatter(lang, plugin); + } + + FORMATTER->storeCurrentSettings(); +} + +void ConfigDialog::markModified() +{ + setModified(true); +} + +void ConfigDialog::setModified(bool modified) +{ + modifiedFlag = modified; + updateModified(); +} + +void ConfigDialog::updateModified() +{ + ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(modifiedFlag); +} + +void ConfigDialog::applyFilter(const QString &filter) +{ + QColor normalColor = ui->categoriesTree->palette().color(QPalette::Active, QPalette::WindowText); + QColor disabledColor = ui->categoriesTree->palette().color(QPalette::Disabled, QPalette::WindowText); + if (filter.isEmpty()) + { + foreach (QTreeWidgetItem* item, getAllCategoryItems()) + item->setForeground(0, normalColor); + + return; + } + + QList widgets = ui->stackedWidget->findChildren(); + QList matchedWidgets; + foreach (QWidget* widget, widgets) + { + if (getFilterString(widget).contains(filter, Qt::CaseInsensitive)) + matchedWidgets << widget; + } + + QHash pageToCategoryItem = buildPageToCategoryItemMap(); + QSet matchedCategories; + foreach (QWidget* page, pageToCategoryItem.keys()) + { + foreach (QWidget* matched, matchedWidgets) + { + if (page->isAncestorOf(matched)) + { + if (!pageToCategoryItem.contains(page)) + { + qCritical() << "Page" << page << "not on page-to-category-item mapping."; + continue; + } + + matchedCategories << pageToCategoryItem[page]; + break; + } + } + } + + foreach (QTreeWidgetItem* item, getAllCategoryItems()) + item->setForeground(0, disabledColor); + + foreach (QTreeWidgetItem* item, matchedCategories) + { + item->setForeground(0, normalColor); + while ((item = item->parent()) != nullptr) + item->setForeground(0, normalColor); + } +} + +QHash ConfigDialog::buildPageToCategoryItemMap() const +{ + QHash pageNameToCategoryItem; + foreach (QTreeWidgetItem* item, getAllCategoryItems()) + pageNameToCategoryItem[item->statusTip(0)] = item; + + QWidget* page = nullptr; + QHash pageToCategoryItem; + for (int i = 0; i < ui->stackedWidget->count(); i++) + { + page = ui->stackedWidget->widget(i); + pageToCategoryItem[page] = pageNameToCategoryItem[page->objectName()]; + } + return pageToCategoryItem; +} + +QList ConfigDialog::getAllCategoryItems() const +{ + return ui->categoriesTree->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); +} + +QList ConfigDialog::getDefaultEditorsForType(DataType::Enum dataType) +{ + QList plugins = PLUGINS->getLoadedPlugins(); + DataType modelDataType; + modelDataType.setType(dataType); + + typedef QPair PluginWithPriority; + QList sortedPlugins; + PluginWithPriority editorWithPrio; + for (MultiEditorWidgetPlugin* plugin : plugins) + { + if (!plugin->validFor(modelDataType)) + continue; + + editorWithPrio.first = plugin->getPriority(modelDataType); + editorWithPrio.second = plugin; + sortedPlugins << editorWithPrio; + } + + qSort(sortedPlugins.begin(), sortedPlugins.end(), [=](const PluginWithPriority& p1, const PluginWithPriority& p2) -> bool + { + return p1.first < p2.first; + }); + + QList results; + for (const PluginWithPriority& p: sortedPlugins) + results << p.second; + + return results; +} + +void ConfigDialog::pageSwitched() +{ + if (ui->stackedWidget->currentWidget() == ui->dataEditorsPage) + { + updateDataTypeEditors(); + return; + } +} + +void ConfigDialog::updateDataTypeEditors() +{ + QString typeName = ui->dataEditorsTypesList->currentItem()->text(); + DataType::Enum typeEnum = DataType::fromString(typeName); + bool usingCustomOrder = false; + QStringList editorsOrder = getPluginNamesFromDataTypeItem(ui->dataEditorsTypesList->currentItem(), &usingCustomOrder); + QList sortedPlugins; + + while (ui->dataEditorsSelectedTabs->count() > 0) + delete ui->dataEditorsSelectedTabs->widget(0); + + ui->dataEditorsAvailableList->clear(); + if (usingCustomOrder) + sortedPlugins = updateCustomDataTypeEditors(editorsOrder); + else + sortedPlugins = updateDefaultDataTypeEditors(typeEnum); + + ui->dataEditorsAvailableList->sortItems(); + + for (MultiEditorWidgetPlugin* plugin : sortedPlugins) + addDataTypeEditor(plugin); +} + +QList ConfigDialog::updateCustomDataTypeEditors(const QStringList& editorsOrder) +{ + // Building plugins list + QList plugins = PLUGINS->getLoadedPlugins(); + QList enabledPlugins; + QListWidgetItem* item = nullptr; + for (MultiEditorWidgetPlugin* plugin : plugins) + { + item = new QListWidgetItem(plugin->getTitle()); + item->setFlags(item->flags()|Qt::ItemIsUserCheckable); + item->setCheckState(editorsOrder.contains(plugin->getName()) ? Qt::Checked : Qt::Unchecked); + item->setData(QListWidgetItem::UserType, plugin->getName()); + if (item->checkState() == Qt::Checked) + enabledPlugins << plugin; + + ui->dataEditorsAvailableList->addItem(item); + } + + qSort(enabledPlugins.begin(), enabledPlugins.end(), [=](MultiEditorWidgetPlugin* p1, MultiEditorWidgetPlugin* p2) -> bool + { + return editorsOrder.indexOf(p1->getName()) < editorsOrder.indexOf(p2->getName()); + }); + + return enabledPlugins; +} + +QList ConfigDialog::updateDefaultDataTypeEditors(DataType::Enum typeEnum) +{ + // Building plugins list + QList plugins = PLUGINS->getLoadedPlugins(); + QList enabledPlugins = getDefaultEditorsForType(typeEnum); + QListWidgetItem* item = nullptr; + for (MultiEditorWidgetPlugin* plugin : plugins) + { + item = new QListWidgetItem(plugin->getTitle()); + item->setFlags(item->flags()|Qt::ItemIsUserCheckable); + item->setCheckState(enabledPlugins.contains(plugin) ? Qt::Checked : Qt::Unchecked); + item->setData(QListWidgetItem::UserType, plugin->getName()); + ui->dataEditorsAvailableList->addItem(item); + } + return enabledPlugins; +} + +void ConfigDialog::addDataTypeEditor(const QString& pluginName) +{ + MultiEditorWidgetPlugin* plugin = dynamic_cast(PLUGINS->getLoadedPlugin(pluginName)); + if (!plugin) + { + qCritical() << "Could not find plugin" << pluginName << " in ConfigDialog::addDataTypeEditor()"; + return; + } + + addDataTypeEditor(plugin); +} + +void ConfigDialog::addDataTypeEditor(MultiEditorWidgetPlugin* plugin) +{ + MultiEditorWidget* editor = plugin->getInstance(); + ui->dataEditorsSelectedTabs->addTab(editor, editor->getTabLabel().replace("&", "&&")); +} + +void ConfigDialog::removeDataTypeEditor(QListWidgetItem* item, const QString& pluginName) +{ + QStringList orderedPlugins = getPluginNamesFromDataTypeItem(item); + int idx = orderedPlugins.indexOf(pluginName); + removeDataTypeEditor(idx); +} + +void ConfigDialog::removeDataTypeEditor(int idx) +{ + if (idx < 0 || idx > (ui->dataEditorsSelectedTabs->count() - 1)) + { + qCritical() << "Index out of range in ConfigDialog::removeDataTypeEditor():" << idx << "(tabs:" << ui->dataEditorsSelectedTabs->count() << ")"; + return; + } + + delete ui->dataEditorsSelectedTabs->widget(idx); +} + +void ConfigDialog::transformDataTypeEditorsToCustomList(QListWidgetItem* typeItem) +{ + DataType::Enum dataType = DataType::fromString(typeItem->text()); + QList plugins = getDefaultEditorsForType(dataType); + + QStringList pluginNames; + for (MultiEditorWidgetPlugin* plugin : plugins) + pluginNames << plugin->getName(); + + setPluginNamesForDataTypeItem(typeItem, pluginNames); +} + +QStringList ConfigDialog::getPluginNamesFromDataTypeItem(QListWidgetItem* typeItem, bool* exists) +{ + QVariant data = typeItem->data(QListWidgetItem::UserType); + if (exists) + *exists = data.isValid(); + + return data.toStringList(); +} + +void ConfigDialog::setPluginNamesForDataTypeItem(QListWidgetItem* typeItem, const QStringList& pluginNames) +{ + updatingDataEditorItem = true; + typeItem->setData(QListWidgetItem::UserType, pluginNames); + updatingDataEditorItem = false; +} + +void ConfigDialog::addDataType(const QString& typeStr) +{ + QListWidgetItem* item = new QListWidgetItem(typeStr); + item->setFlags(item->flags()|Qt::ItemIsEditable); + ui->dataEditorsTypesList->addItem(item); + ui->dataEditorsTypesList->setCurrentRow(ui->dataEditorsTypesList->count() - 1, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); + markModified(); +} + +void ConfigDialog::rollbackPluginConfigs() +{ + CfgMain* mainCfg = nullptr; + for (UiConfiguredPlugin* plugin : pluginConfigMappers.keys()) + { + mainCfg = plugin->getMainUiConfig(); + if (mainCfg) + mainCfg->rollback(); + } +} + +void ConfigDialog::commitPluginConfigs() +{ + CfgMain* mainCfg = nullptr; + for (UiConfiguredPlugin* plugin : pluginConfigMappers.keys()) + { + mainCfg = plugin->getMainUiConfig(); + if (mainCfg) + { + mainCfg->commit(); + mainCfg->begin(); // be prepared for further changes after "Apply" + } + } +} + +void ConfigDialog::updateDataTypeListState() +{ + bool listEditingEnabled = ui->dataEditorsTypesList->selectedItems().size() > 0 && ui->dataEditorsTypesList->currentItem()->flags().testFlag(Qt::ItemIsEditable); + dataEditRenameAction->setEnabled(listEditingEnabled); + dataEditDeleteAction->setEnabled(listEditingEnabled); + + bool orderEditingEnabled = ui->dataEditorsTypesList->selectedItems().size() > 0; + ui->dataEditorsAvailableList->setEnabled(orderEditingEnabled); + ui->dataEditorsSelectedTabs->setEnabled(orderEditingEnabled); +} + +void ConfigDialog::dataEditorItemEdited(QListWidgetItem* item) +{ + if (updatingDataEditorItem) + return; + + updatingDataEditorItem = true; + QString txt = item->text().toUpper(); + if (DataType::getAllNames().contains(txt)) + txt += "_"; + + while (ui->dataEditorsTypesList->findItems(txt, Qt::MatchExactly).size() > 1) + txt += "_"; + + item->setText(txt); + updatingDataEditorItem = false; +} + +void ConfigDialog::dataEditorAvailableChanged(QListWidgetItem* item) +{ + QListWidgetItem* typeItem = ui->dataEditorsTypesList->currentItem(); + if (!typeItem) + return; + + bool exists = false; + QStringList pluginNames = getPluginNamesFromDataTypeItem(typeItem, &exists); + if (!exists) + { + transformDataTypeEditorsToCustomList(typeItem); + pluginNames = getPluginNamesFromDataTypeItem(typeItem); + } + + QString pluginName = item->data(QListWidgetItem::UserType).toString(); + Qt::CheckState state = item->checkState(); + if (pluginNames.contains(pluginName) && state == Qt::Unchecked) + { + removeDataTypeEditor(typeItem, pluginName); + pluginNames.removeOne(pluginName); + + } + else if (!pluginNames.contains(pluginName) && state == Qt::Checked) + { + addDataTypeEditor(pluginName); + pluginNames << pluginName; + } + + setPluginNamesForDataTypeItem(typeItem, pluginNames); +} + +void ConfigDialog::dataEditorTabsOrderChanged(int from, int to) +{ + QListWidgetItem* typeItem = ui->dataEditorsTypesList->currentItem(); + if (!typeItem) + return; + + bool exists = false; + QStringList pluginNames = getPluginNamesFromDataTypeItem(typeItem, &exists); + if (!exists) + { + transformDataTypeEditorsToCustomList(typeItem); + pluginNames = getPluginNamesFromDataTypeItem(typeItem); + } + + int pluginSize = pluginNames.size(); + if (from >= pluginSize || to >= pluginSize) + { + qCritical() << "Tabse moved out of range. in ConfigDialog::dataEditorTabsOrderChanged(). Range was: " << pluginSize << "and indexes were:" << from << to; + return; + } + + QString pluginName = pluginNames[from]; + pluginNames.removeAt(from); + pluginNames.insert(to, pluginName); + + setPluginNamesForDataTypeItem(typeItem, pluginNames); +} + +void ConfigDialog::addDataType() +{ + addDataType(""); + renameDataType(); +} + +void ConfigDialog::renameDataType() +{ + QListWidgetItem* item = ui->dataEditorsTypesList->currentItem(); + if (!item) + return; + + ui->dataEditorsTypesList->editItem(item); +} + +void ConfigDialog::delDataType() +{ + QListWidgetItem* item = ui->dataEditorsTypesList->currentItem(); + if (!item) + return; + + int row = ui->dataEditorsTypesList->currentRow(); + delete ui->dataEditorsTypesList->takeItem(row); + + if (ui->dataEditorsTypesList->count() > 0) + { + if (ui->dataEditorsTypesList->count() <= row) + { + row--; + if (row < 0) + row = 0; + } + + ui->dataEditorsTypesList->setCurrentRow(row, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); + } + + updateDataTypeListState(); + markModified(); +} + +void ConfigDialog::dataTypesHelp() +{ + static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Customizing_data_type_editors"); + QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode)); +} + +void ConfigDialog::updateActiveFormatterState() +{ + CodeFormatterPlugin* plugin = nullptr; + QTreeWidgetItem* item = nullptr; + QComboBox* combo = nullptr; + QToolButton* button = nullptr; + QString lang; + QString pluginName; + for (int i = 0, total = ui->formatterPluginsTree->topLevelItemCount(); i < total; ++i) + { + item = ui->formatterPluginsTree->topLevelItem(i); + lang = item->text(0); + + combo = formatterLangToPluginComboMap[lang]; + button = formatterLangToConfigButtonMap[lang]; + if (!button) + { + qCritical() << "Could not find button for lang " << lang << " in updateActiveFormatterState()"; + continue; + } + + if (!combo) + { + qCritical() << "Could not find combo for lang " << lang << " in updateActiveFormatterState()"; + button->setEnabled(false); + continue; + } + + pluginName = combo->currentData().toString(); + plugin = dynamic_cast(PLUGINS->getLoadedPlugin(pluginName)); + if (!plugin) + { + qCritical() << "Could not find plugin for lang " << lang << " in updateActiveFormatterState()"; + button->setEnabled(false); + continue; + } + + button->setEnabled(dynamic_cast(plugin)); + } +} + +void ConfigDialog::configureFormatter(const QString& pluginTitle) +{ + QTreeWidgetItem* item = getItemByTitle(pluginTitle); + if (!item) + return; + + ui->categoriesTree->setCurrentItem(item); +} + +void ConfigDialog::activeFormatterChanged() +{ + markModified(); + updateActiveFormatterState(); +} + +void ConfigDialog::detailsClicked(const QString& pluginName) +{ + static const QString details = QStringLiteral( + "" + "" + "" + "" + "" + "%2" + "
    %1
    "); + static const QString row = QStringLiteral("%1%2"); + static const QString hline = QStringLiteral("
    "); + + PluginType* type = PLUGINS->getPluginType(pluginName); + Q_ASSERT(type != nullptr); + + // Rows + QStringList rows; + rows << row.arg(tr("Description:", "plugin details")).arg(PLUGINS->getDescription(pluginName)); + rows << row.arg(tr("Category:", "plugin details")).arg(type->getTitle()); + rows << row.arg(tr("Version:", "plugin details")).arg(PLUGINS->getPrintableVersion(pluginName)); + rows << row.arg(tr("Author:", "plugin details")).arg(PLUGINS->getAuthor(pluginName)); + rows << hline; + rows << row.arg(tr("Internal name:", "plugin details")).arg(pluginName); + rows << row.arg(tr("Dependencies:", "plugin details")).arg(PLUGINS->getDependencies(pluginName).join(", ")); + rows << row.arg(tr("Conflicts:", "plugin details")).arg(PLUGINS->getConflicts(pluginName).join(", ")); + + // Message + QString pluginDetails = details.arg(PLUGINS->getTitle(pluginName)).arg(rows.join("")); + QMessageBox::information(this, tr("Plugin details"), pluginDetails); +} + +void ConfigDialog::failedToLoadPlugin(const QString& pluginName) +{ + QTreeWidgetItem* theItem = itemToPluginNameMap.valueByRight(pluginName); + if (!theItem) + { + qWarning() << "Plugin" << pluginName << "failed to load, but it could not be found on the plugins list in ConfigDialog."; + return; + } + + theItem->setCheckState(0, Qt::Unchecked); +} + +void ConfigDialog::codeFormatterUnloaded() +{ + refreshFormattersPage(); +} + +void ConfigDialog::codeFormatterLoaded() +{ + refreshFormattersPage(); +} + +void ConfigDialog::loadUnloadPlugin(QTreeWidgetItem* item, int column) +{ + if (column != 0) + return; + + QString pluginName = itemToPluginNameMap.valueByLeft(item); + if (PLUGINS->isBuiltIn(pluginName)) + return; + + bool wasLoaded = PLUGINS->isLoaded(pluginName); + + if (wasLoaded == (item->checkState(0) == Qt::Checked)) + return; + + if (wasLoaded) + PLUGINS->unload(pluginName); + else + PLUGINS->load(pluginName); + + markModified(); +} + +void ConfigDialog::pluginAboutToUnload(Plugin* plugin, PluginType* type) +{ + // Deinit tree item + QTreeWidgetItem* typeItem = getPluginsCategoryItem(type); + QTreeWidgetItem* pluginItem = getPluginItem(plugin); + if (pluginItem) + { + typeItem->removeChild(pluginItem); + pluginToItemMap.remove(plugin); + } + + // Notifiable plugin + ConfigNotifiablePlugin* notifiablePlugin = dynamic_cast(plugin); + if (notifiablePlugin && notifiablePlugins.contains(notifiablePlugin)) + notifiablePlugins.removeOne(notifiablePlugin); + + // Deinit page + deinitPluginPage(plugin); + + // Update tree categories + updatePluginCategoriesVisibility(); +} + +void ConfigDialog::pluginLoaded(Plugin* plugin, PluginType* type, bool skipConfigLoading) +{ + // Update formatters page + if (type->isForPluginType()) + codeFormatterLoaded(); + + // Init page + if (!initPluginPage(plugin, skipConfigLoading)) + return; + + // Init tree item + QTreeWidgetItem* typeItem = getPluginsCategoryItem(type); + QTreeWidgetItem* pluginItem = new QTreeWidgetItem({plugin->getTitle()}); + pluginItem->setStatusTip(0, plugin->getName()); + typeItem->addChild(pluginItem); + pluginToItemMap[plugin] = pluginItem; + + // Update tree categories + updatePluginCategoriesVisibility(); + + // Notifiable plugin + ConfigNotifiablePlugin* notifiablePlugin = dynamic_cast(plugin); + if (notifiablePlugin) + notifiablePlugins << notifiablePlugin; +} + +void ConfigDialog::pluginUnloaded(const QString& pluginName, PluginType* type) +{ + UNUSED(pluginName); + + // Update formatters page + if (type->isForPluginType()) + codeFormatterUnloaded(); +} + +void ConfigDialog::updatePluginCategoriesVisibility() +{ + QTreeWidgetItem* categories = getPluginsCategoryItem(); + for (int i = 0; i < categories->childCount(); i++) + updatePluginCategoriesVisibility(categories->child(i)); +} + +void ConfigDialog::updateBuiltInPluginsVisibility() +{ + bool hideBuiltIn = ui->hideBuiltInPluginsCheck->isChecked(); + QHashIterator it = itemToPluginNameMap.iterator(); + while (it.hasNext()) + { + it.next(); + if (PLUGINS->isBuiltIn(it.value())) + ui->pluginsList->setItemHidden(it.key(), hideBuiltIn); + else + ui->pluginsList->setItemHidden(it.key(), false); + } +} + +void ConfigDialog::applyShortcutsFilter(const QString &filter) +{ + QTreeWidgetItem* categoryItem = nullptr; + QTreeWidgetItem* item = nullptr; + QKeySequenceEdit* seqEdit = nullptr; + bool empty = filter.isEmpty(); + bool visible = true; + int foundInCategory = 0; + for (int i = 0, total_i = ui->shortcutsTable->topLevelItemCount(); i < total_i; ++i) + { + foundInCategory = 0; + categoryItem = ui->shortcutsTable->topLevelItem(i); + for (int j = 0 , total_j = categoryItem->childCount(); j < total_j; ++j) + { + item = categoryItem->child(j); + seqEdit = dynamic_cast(ui->shortcutsTable->itemWidget(item, 1)); + visible = empty || item->text(0).contains(filter, Qt::CaseInsensitive) || + seqEdit->keySequence().toString().contains(filter, Qt::CaseInsensitive); + + item->setHidden(!visible); + if (visible) + foundInCategory++; + } + + categoryItem->setHidden(foundInCategory == 0); + } +} + +void ConfigDialog::updatePluginCategoriesVisibility(QTreeWidgetItem* categoryItem) +{ + categoryItem->setHidden(categoryItem->childCount() == 0); +} + +QString ConfigDialog::collectLoadedPlugins() const +{ + QStringList loaded; + QHashIterator it = itemToPluginNameMap.iterator(); + while (it.hasNext()) + { + it.next(); + loaded << (it.value() + "=" + ((it.key()->checkState(0) == Qt::Checked) ? "1" : "0")); + } + + return loaded.join(","); +} + +void ConfigDialog::initPageMap() +{ + int pages = ui->stackedWidget->count(); + QWidget* widget = nullptr; + for (int i = 0; i < pages; i++) + { + widget = ui->stackedWidget->widget(i); + nameToPage[widget->objectName()] = widget; + } +} + +void ConfigDialog::initInternalCustomConfigWidgets() +{ + QList customWidgets; + customWidgets << new StyleConfigWidget(); + customWidgets << new ListToStringListHash(&CFG_UI.General.DataEditorsOrder); + configMapper->setInternalCustomConfigWidgets(customWidgets); +} + +void ConfigDialog::initFormatterPlugins() +{ + ui->formatterPluginsTree->header()->setSectionsMovable(false); + ui->formatterPluginsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->formatterPluginsTree->resizeColumnToContents(1); + ui->formatterPluginsTree->resizeColumnToContents(2); + + refreshFormattersPage(); +} + +void ConfigDialog::refreshFormattersPage() +{ + ui->formatterPluginsTree->clear(); + + QHash activeFormatters = CFG_CORE.General.ActiveCodeFormatter.get(); + + QList plugins = PLUGINS->getLoadedPlugins(); + QHash> groupedPlugins; + for (CodeFormatterPlugin* plugin : plugins) + groupedPlugins[plugin->getLanguage()] << plugin; + + formatterLangToPluginComboMap.clear(); + formatterLangToConfigButtonMap.clear(); + int row = 0; + QTreeWidgetItem* item = nullptr; + QComboBox* combo = nullptr; + QToolButton* configButton = nullptr; + QStringList pluginTitles; + QStringList pluginNames; + QStringList sortedPluginNames; + QString selectedPluginName; + QModelIndex index; + QString groupName; + QHashIterator> it(groupedPlugins); + while (it.hasNext()) + { + it.next(); + groupName = it.key(); + + item = new QTreeWidgetItem({groupName}); + ui->formatterPluginsTree->addTopLevelItem(item); + + pluginNames.clear(); + pluginTitles.clear(); + for (CodeFormatterPlugin* plugin : it.value()) + { + pluginNames << plugin->getName(); + pluginTitles << plugin->getTitle(); + } + sortedPluginNames = pluginNames; + qSort(sortedPluginNames); + + combo = new QComboBox(ui->formatterPluginsTree); + for (int i = 0, total = pluginNames.size(); i < total; ++i) + combo->addItem(pluginTitles[i], pluginNames[i]); + + connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(activeFormatterChanged())); + index = ui->formatterPluginsTree->model()->index(row, 1); + ui->formatterPluginsTree->setIndexWidget(index, combo); + formatterLangToPluginComboMap[groupName] = combo; + + if (activeFormatters.contains(groupName) && pluginNames.contains(activeFormatters[groupName].toString())) + { + selectedPluginName = activeFormatters[groupName].toString(); + } + else + { + // Pick first from sorted list and put it to combobox + selectedPluginName = sortedPluginNames.first(); + } + + configButton = new QToolButton(ui->formatterPluginsTree); + configButton->setIcon(ICONS.CONFIGURE); + index = ui->formatterPluginsTree->model()->index(row, 2); + ui->formatterPluginsTree->setIndexWidget(index, configButton); + connect(configButton, &QToolButton::clicked, [this, combo]() {configureFormatter(combo->currentText());}); + formatterLangToConfigButtonMap[groupName] = configButton; + + combo->setCurrentIndex(pluginNames.indexOf(selectedPluginName)); + + row++; + } + + updateActiveFormatterState(); +} + +void ConfigDialog::applyStyle(QWidget *widget, QStyle *style) +{ + widget->setStyle(style); + foreach (QObject* child, widget->children()) + { + if (!qobject_cast(child)) + continue; + + applyStyle(qobject_cast(child), style); + } +} + +QTreeWidgetItem* ConfigDialog::getPluginsCategoryItem() const +{ + QTreeWidgetItem* item = nullptr; + for (int i = 0; i < ui->categoriesTree->topLevelItemCount(); i++) + { + item = ui->categoriesTree->topLevelItem(i); + if (item->statusTip(0) == ui->pluginsPage->objectName()) + return item; + } + Q_ASSERT_X(true, "ConfigDialog", "No Plugins toplevel item in config categories tree!"); + return nullptr; +} + +QTreeWidgetItem* ConfigDialog::getPluginsCategoryItem(PluginType* type) const +{ + if (!pluginTypeToItemMap.contains(type)) + return nullptr; + + return pluginTypeToItemMap[type]; +} + +QTreeWidgetItem* ConfigDialog::getPluginItem(Plugin* plugin) const +{ + if (!pluginToItemMap.contains(plugin)) + return nullptr; + + return pluginToItemMap[plugin]; +} + +QTreeWidgetItem* ConfigDialog::createPluginsTypeItem(const QString& widgetName, const QString& title) const +{ + if (FORMS->hasWidget(widgetName)) + return new QTreeWidgetItem({title}); + + QTreeWidgetItem* pluginsCategoryItem = getPluginsCategoryItem(); + QTreeWidgetItem* item = nullptr; + for (int i = 0; i < pluginsCategoryItem->childCount(); i++) + { + item = pluginsCategoryItem->child(i); + if (item->statusTip(0) == widgetName) + return item; + } + return nullptr; + +} + +QTreeWidgetItem* ConfigDialog::getItemByTitle(const QString& title) const +{ + QList items = ui->categoriesTree->findItems(title, Qt::MatchExactly|Qt::MatchRecursive); + if (items.size() == 0) + return nullptr; + + return items.first(); +} + +void ConfigDialog::switchPage(QTreeWidgetItem *item) +{ + if (isPluginCategoryItem((item))) + { + switchPageToPlugin(item); + return; + } + + QString name = item->statusTip(0); + if (!nameToPage.contains(name)) + { + qWarning() << "Switched page to item" << name << "but there's no such named page defined in ConfigDialog."; + return; + } + + ui->stackedWidget->setCurrentWidget(nameToPage[name]); +} + +void ConfigDialog::switchPageToPlugin(QTreeWidgetItem *item) +{ + QString pluginName = item->statusTip(0); + if (!nameToPage.contains(pluginName)) + { + qCritical() << "No plugin page available for plugin:" << pluginName; + return; + } + ui->stackedWidget->setCurrentWidget(nameToPage[pluginName]); +} + +void ConfigDialog::initPlugins() +{ + QTreeWidgetItem *item = getPluginsCategoryItem(); + + // Recreate + QTreeWidgetItem *typeItem = nullptr; + foreach (PluginType* pluginType, PLUGINS->getPluginTypes()) + { + typeItem = createPluginsTypeItem(pluginType->getConfigUiForm(), pluginType->getTitle()); + if (!typeItem) + continue; + + item->addChild(typeItem); + pluginTypeToItemMap[pluginType] = typeItem; + + foreach (Plugin* plugin, pluginType->getLoadedPlugins()) + pluginLoaded(plugin, pluginType, true); + } + + updatePluginCategoriesVisibility(); + + connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(pluginLoaded(Plugin*,PluginType*))); + connect(PLUGINS, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(pluginAboutToUnload(Plugin*,PluginType*))); +} + +void ConfigDialog::initPluginsPage() +{ + setValidStateTooltip(ui->pluginsList, tr("Plugins are loaded/unloaded immediately when checked/unchecked, " + "but modified list of plugins to load at startup is not saved until " + "you commit the whole configuration dialog.")); + + QTreeWidgetItem* category = nullptr; + QTreeWidgetItem* item = nullptr; + QFont font; + QModelIndex categoryIndex; + QModelIndex itemIndex; + int itemRow; + int categoryRow; + bool builtIn; + QLabel* detailsLabel = nullptr; + QString title; + QSize itemSize; + QStringList pluginNames; + + // Font and metrics + item = new QTreeWidgetItem({""}); + font = item->font(0); + + QFontMetrics fm(font); + itemSize = QSize(-1, (fm.ascent() + fm.descent() + 4)); + + delete item; + + // Creating... + ui->pluginsList->header()->setSectionsMovable(false); + ui->pluginsList->header()->setSectionResizeMode(0, QHeaderView::Stretch); + + QBrush categoryBg = ui->pluginsList->palette().button(); + QBrush categoryFg = ui->pluginsList->palette().buttonText(); + + connect(ui->pluginsList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(loadUnloadPlugin(QTreeWidgetItem*,int))); + connect(PLUGINS, SIGNAL(failedToLoad(QString)), this, SLOT(failedToLoadPlugin(QString))); + + categoryRow = 0; + QList pluginTypes = PLUGINS->getPluginTypes(); + qSort(pluginTypes.begin(), pluginTypes.end(), PluginType::nameLessThan); + foreach (PluginType* pluginType, pluginTypes) + { + category = new QTreeWidgetItem({pluginType->getTitle()}); + font.setItalic(false); + font.setBold(true); + category->setFont(0, font); + for (int i = 0; i < 2; i++) + { + category->setBackground(i, categoryBg); + category->setForeground(i, categoryFg); + } + category->setSizeHint(0, itemSize); + ui->pluginsList->addTopLevelItem(category); + + categoryIndex = ui->pluginsList->model()->index(categoryRow, 0); + categoryRow++; + + itemRow = 0; + pluginNames = pluginType->getAllPluginNames(); + qSort(pluginNames); + foreach (const QString& pluginName, pluginNames) + { + builtIn = PLUGINS->isBuiltIn(pluginName); + title = PLUGINS->getTitle(pluginName); + if (builtIn) + title += tr(" (built-in)", "plugins manager in configuration dialog"); + + item = new QTreeWidgetItem({title}); + item->setCheckState(0, PLUGINS->isLoaded(pluginName) ? Qt::Checked : Qt::Unchecked); + item->setSizeHint(0, itemSize); + if (builtIn) + item->setDisabled(true); + + category->addChild(item); + + itemToPluginNameMap.insert(item, pluginName); + + // Details button + detailsLabel = new QLabel(QString("%2 ").arg(pluginName).arg(tr("Details")), ui->pluginsList); + detailsLabel->setAlignment(Qt::AlignRight); + itemIndex = ui->pluginsList->model()->index(itemRow, 1, categoryIndex); + ui->pluginsList->setIndexWidget(itemIndex, detailsLabel); + + connect(detailsLabel, SIGNAL(linkActivated(QString)), this, SLOT(detailsClicked(QString))); + + itemRow++; + } + + if (itemRow == 0) + { + item = new QTreeWidgetItem({tr("No plugins in this category.")}); + item->setDisabled(true); + item->setSizeHint(0, itemSize); + + font.setItalic(true); + font.setBold(false); + item->setFont(0, font); + + category->addChild(item); + } + + category->setExpanded(true); + } +} + +bool ConfigDialog::initPluginPage(Plugin* plugin, bool skipConfigLoading) +{ + if (!dynamic_cast(plugin)) + return false; + + UiConfiguredPlugin* cfgPlugin = dynamic_cast(plugin); + QString pluginName = plugin->getName(); + QString formName = cfgPlugin->getConfigUiForm(); + QWidget* widget = FORMS->createWidget(formName); + if (!widget) + { + qWarning() << "Could not load plugin UI file" << formName << "for plugin:" << pluginName; + return false; + } + + nameToPage[pluginName] = widget; + ui->stackedWidget->addWidget(widget); + CfgMain* mainConfig = cfgPlugin->getMainUiConfig(); + if (mainConfig) + { + pluginConfigMappers[cfgPlugin] = new ConfigMapper(mainConfig); + pluginConfigMappers[cfgPlugin]->bindToConfig(widget); + mainConfig->begin(); + } + else if (!skipConfigLoading) + { + configMapper->loadToWidget(widget); + } + + cfgPlugin->configDialogOpen(); + return true; +} + +void ConfigDialog::deinitPluginPage(Plugin* plugin) +{ + QString pluginName = plugin->getName(); + if (!nameToPage.contains(pluginName)) + return; + + if (!dynamic_cast(plugin)) + { + UiConfiguredPlugin* cfgPlugin = dynamic_cast(plugin); + CfgMain* mainCfg = cfgPlugin->getMainUiConfig(); + if (mainCfg) + mainCfg->rollback(); + + cfgPlugin->configDialogClosed(); + + if (pluginConfigMappers.contains(cfgPlugin)) + { + delete pluginConfigMappers[cfgPlugin]; + pluginConfigMappers.remove(cfgPlugin); + } + } + + QWidget* widget = nameToPage[pluginName]; + nameToPage.remove(pluginName); + ui->stackedWidget->removeWidget(widget); + delete widget; +} + +void ConfigDialog::initDataEditors() +{ + ui->dataEditorsAvailableList->setSpacing(1); + + QHash editorsOrder = CFG_UI.General.DataEditorsOrder.get(); + QSet dataTypeSet = editorsOrder.keys().toSet(); + dataTypeSet += DataType::getAllNames().toSet(); + QStringList dataTypeList = dataTypeSet.toList(); + qSort(dataTypeList); + + QListWidgetItem* item = nullptr; + for (const QString& type : dataTypeList) + { + item = new QListWidgetItem(type); + if (!DataType::getAllNames().contains(type)) + item->setFlags(item->flags()|Qt::ItemIsEditable); + + ui->dataEditorsTypesList->addItem(item); + } + + QAction* act = new QAction(ICONS.INSERT_DATATYPE, tr("Add new data type"), ui->dataEditorsTypesToolbar); + connect(act, SIGNAL(triggered()), this, SLOT(addDataType())); + ui->dataEditorsTypesToolbar->addAction(act); + + dataEditRenameAction = new QAction(ICONS.RENAME_DATATYPE, tr("Rename selected data type"), ui->dataEditorsTypesToolbar); + connect(dataEditRenameAction, SIGNAL(triggered()), this, SLOT(renameDataType())); + ui->dataEditorsTypesToolbar->addAction(dataEditRenameAction); + + dataEditDeleteAction = new QAction(ICONS.DELETE_DATATYPE, tr("Delete selected data type"), ui->dataEditorsTypesToolbar); + connect(dataEditDeleteAction, SIGNAL(triggered()), this, SLOT(delDataType())); + ui->dataEditorsTypesToolbar->addAction(dataEditDeleteAction); + + act = new QAction(ICONS.HELP, tr("Help for configuring data type editors"), ui->dataEditorsTypesToolbar); + connect(act, SIGNAL(triggered()), this, SLOT(dataTypesHelp())); + ui->dataEditorsTypesToolbar->addAction(act); + + connect(ui->dataEditorsTypesList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateDataTypeEditors())); + connect(ui->dataEditorsTypesList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateDataTypeListState())); + connect(ui->dataEditorsTypesList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(dataEditorItemEdited(QListWidgetItem*))); + connect(ui->dataEditorsAvailableList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(dataEditorAvailableChanged(QListWidgetItem*))); + connect(ui->dataEditorsSelectedTabs->tabBar(), SIGNAL(tabMoved(int,int)), this, SLOT(dataEditorTabsOrderChanged(int,int))); + + ui->dataEditorsTypesList->setCurrentRow(0, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); + updateDataTypeListState(); +} + +void ConfigDialog::initShortcuts() +{ + ui->shortcutsTable->header()->setSectionsMovable(false); + ui->shortcutsTable->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->shortcutsTable->header()->setSectionResizeMode(1, QHeaderView::Fixed); + ui->shortcutsTable->header()->setSectionResizeMode(2, QHeaderView::Fixed); + ui->shortcutsTable->header()->resizeSection(1, 150); + ui->shortcutsTable->header()->resizeSection(2, 26); + + ui->shortcutsFilterEdit->setClearButtonEnabled(true); + new UserInputFilter(ui->shortcutsFilterEdit, this, SLOT(applyShortcutsFilter(QString))); + + static const QString metaName = CFG_SHORTCUTS_METANAME; + QList categories; + for (CfgMain* cfgMain : CfgMain::getInstances()) + { + if (cfgMain->getMetaName() != metaName) + continue; + + for (CfgCategory* cat : cfgMain->getCategories().values()) + categories << cat; + } + + qSort(categories.begin(), categories.end(), [](CfgCategory* cat1, CfgCategory* cat2) -> bool + { + return cat1->getTitle().compare(cat2->getTitle()) < 0; + }); + + for (CfgCategory* cat : categories) + initShortcuts(cat); +} + +void ConfigDialog::initShortcuts(CfgCategory *cfgCategory) +{ + QTreeWidgetItem* item = nullptr; + QFont font; + QModelIndex categoryIndex; + QModelIndex itemIndex; + QKeySequenceEdit *sequenceEdit = nullptr; + QToolButton* clearButton = nullptr; + QString title; + QSize itemSize; + + // Font and metrics + item = new QTreeWidgetItem({""}); + font = item->font(0); + + QFontMetrics fm(font); + itemSize = QSize(-1, (fm.ascent() + fm.descent() + 4)); + + delete item; + + // Creating... + QBrush categoryBg = ui->shortcutsTable->palette().button(); + QBrush categoryFg = ui->shortcutsTable->palette().buttonText(); + + QTreeWidgetItem* category = new QTreeWidgetItem({cfgCategory->getTitle()}); + font.setItalic(false); + font.setBold(true); + category->setFont(0, font); + for (int i = 0; i < 3; i++) + { + category->setBackground(i, categoryBg); + category->setForeground(i, categoryFg); + } + category->setSizeHint(0, itemSize); + category->setFlags(category->flags() ^ Qt::ItemIsSelectable); + ui->shortcutsTable->addTopLevelItem(category); + + int categoryRow = ui->shortcutsTable->topLevelItemCount() - 1; + categoryIndex = ui->shortcutsTable->model()->index(categoryRow, 0); + + int itemRow = 0; + QStringList entryNames = cfgCategory->getEntries().keys(); + qSort(entryNames); + foreach (const QString& entryName, entryNames) + { + // Title + title = cfgCategory->getEntries()[entryName]->getTitle(); + item = new QTreeWidgetItem(category, {title}); + + // Key edit + sequenceEdit = new QKeySequenceEdit(ui->shortcutsTable); + sequenceEdit->setFixedWidth(150); + sequenceEdit->setProperty("cfg", cfgCategory->getEntries()[entryName]->getFullKey()); + itemIndex = ui->shortcutsTable->model()->index(itemRow, 1, categoryIndex); + ui->shortcutsTable->setIndexWidget(itemIndex, sequenceEdit); + configMapper->addExtraWidget(sequenceEdit); + + // Clear button + clearButton = new QToolButton(ui->shortcutsTable); + clearButton->setIcon(ICONS.CLEAR_LINEEDIT); + connect(clearButton, &QToolButton::clicked, [this, sequenceEdit]() + { + sequenceEdit->clear(); + this->markModified(); + + }); + itemIndex = ui->shortcutsTable->model()->index(itemRow, 2, categoryIndex); + ui->shortcutsTable->setIndexWidget(itemIndex, clearButton); + + itemRow++; + } + + category->setExpanded(true); +} + +bool ConfigDialog::isPluginCategoryItem(QTreeWidgetItem *item) const +{ + return item->parent() && item->parent()->parent() && item->parent()->parent() == getPluginsCategoryItem(); +} + +void ConfigDialog::updateStylePreview() +{ + ui->previewWidget->parentWidget()->layout()->removeWidget(ui->previewWidget); + ui->previewTabs->currentWidget()->layout()->addWidget(ui->previewWidget); + ui->previewWidget->setEnabled(ui->previewTabs->currentIndex() == 0); + + QStyle* previousStyle = previewStyle; + previewStyle = QStyleFactory::create(ui->activeStyleCombo->currentText()); + if (!previewStyle) + { + qWarning() << "Could not create style:" << ui->activeStyleCombo->currentText(); + return; + } + + applyStyle(ui->activeStylePreviewGroup, previewStyle); + + if (previousStyle) + delete previousStyle; +} + +void ConfigDialog::apply() +{ + if (modifiedFlag) + save(); + + setModified(false); +} + +void ConfigDialog::accept() +{ + apply(); + QDialog::accept(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h new file mode 100644 index 0000000..95e9f1a --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h @@ -0,0 +1,141 @@ +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include "config_builder.h" +#include "datatype.h" +#include "common/bihash.h" +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class ConfigDialog; +} + +class QListWidgetItem; +class QTreeWidgetItem; +class CustomConfigWidgetPlugin; +class QSignalMapper; +class Plugin; +class PluginType; +class QComboBox; +class QToolButton; +class QTreeWidget; +class QListWidget; +class QTableWidget; +class ConfigMapper; +class MultiEditorWidgetPlugin; +class ConfigNotifiablePlugin; +class UiConfiguredPlugin; + +class GUI_API_EXPORT ConfigDialog : public QDialog +{ + Q_OBJECT + + public: + explicit ConfigDialog(QWidget *parent = 0); + ~ConfigDialog(); + + void configureDataEditors(const QString& dataTypeString); + + static QString getFilterString(QWidget* widget); + static QString getFilterString(QComboBox* widget); + static QString getFilterString(QTreeWidget* widget); + static QString getFilterString(QListWidget* widget); + static QString getFilterString(QTableWidget* widget); + + private: + void init(); + void load(); + void initPageMap(); + void initInternalCustomConfigWidgets(); + void initFormatterPlugins(); + void initPlugins(); + void initPluginsPage(); + bool initPluginPage(Plugin* plugin, bool skipConfigLoading); + void deinitPluginPage(Plugin* pluginName); + void initDataEditors(); + void initShortcuts(); + void initShortcuts(CfgCategory* cfgCategory); + void applyStyle(QWidget* widget, QStyle* style); + QTreeWidgetItem* getPluginsCategoryItem() const; + QTreeWidgetItem* getPluginsCategoryItem(PluginType* type) const; + QTreeWidgetItem* getPluginItem(Plugin* plugin) const; + QTreeWidgetItem* createPluginsTypeItem(const QString& widgetName, const QString& title) const; + QTreeWidgetItem* getItemByTitle(const QString& title) const; + void switchPageToPlugin(QTreeWidgetItem* item); + bool isPluginCategoryItem(QTreeWidgetItem *item) const; + void codeFormatterUnloaded(); + void codeFormatterLoaded(); + void updatePluginCategoriesVisibility(QTreeWidgetItem* categoryItem); + QString collectLoadedPlugins() const; + QHash buildPageToCategoryItemMap() const; + QList getAllCategoryItems() const; + QList getDefaultEditorsForType(DataType::Enum dataType); + QList updateCustomDataTypeEditors(const QStringList& editorsOrder); + QList updateDefaultDataTypeEditors(DataType::Enum typeEnum); + void addDataTypeEditor(const QString& pluginName); + void addDataTypeEditor(MultiEditorWidgetPlugin* plugin); + void removeDataTypeEditor(QListWidgetItem* item, const QString& pluginName); + void removeDataTypeEditor(int idx); + void transformDataTypeEditorsToCustomList(QListWidgetItem* typeItem); + QStringList getPluginNamesFromDataTypeItem(QListWidgetItem* typeItem, bool* exists = nullptr); + void setPluginNamesForDataTypeItem(QListWidgetItem* typeItem, const QStringList& pluginNames); + void addDataType(const QString& typeStr); + void rollbackPluginConfigs(); + void commitPluginConfigs(); + + Ui::ConfigDialog *ui = nullptr; + QStyle* previewStyle = nullptr; + QHash nameToPage; + BiHash itemToPluginNameMap; + QHash pluginTypeToItemMap; + QHash pluginToItemMap; + QHash formatterLangToPluginComboMap; + QHash formatterLangToConfigButtonMap; + ConfigMapper* configMapper = nullptr; + QHash pluginConfigMappers; + QAction* dataEditRenameAction = nullptr; + QAction* dataEditDeleteAction = nullptr; + bool updatingDataEditorItem = false; + bool modifiedFlag = false; + QList notifiablePlugins; + + private slots: + void refreshFormattersPage(); + void pageSwitched(); + void updateDataTypeEditors(); + void updateDataTypeListState(); + void dataEditorItemEdited(QListWidgetItem* item); + void dataEditorAvailableChanged(QListWidgetItem* item); + void dataEditorTabsOrderChanged(int from, int to); + void addDataType(); + void renameDataType(); + void delDataType(); + void dataTypesHelp(); + void switchPage(QTreeWidgetItem* item); + void updateStylePreview(); + void apply(); + void save(); + void storeSelectedFormatters(); + void markModified(); + void setModified(bool modified); + void updateModified(); + void applyFilter(const QString& filter); + void updateActiveFormatterState(); + void configureFormatter(const QString& pluginTitle); + void activeFormatterChanged(); + void detailsClicked(const QString& pluginName); + void failedToLoadPlugin(const QString& pluginName); + void loadUnloadPlugin(QTreeWidgetItem* item, int column); + void pluginAboutToUnload(Plugin* plugin, PluginType* type); + void pluginLoaded(Plugin* plugin, PluginType* type, bool skipConfigLoading = false); + void pluginUnloaded(const QString& pluginName, PluginType* type); + void updatePluginCategoriesVisibility(); + void updateBuiltInPluginsVisibility(); + void applyShortcutsFilter(const QString& filter); + + public slots: + void accept(); +}; + +#endif // CONFIGDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui new file mode 100644 index 0000000..82cc286 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui @@ -0,0 +1,1923 @@ + + + ConfigDialog + + + + 0 + 0 + 770 + 539 + + + + Configuration + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::PreventContextMenu + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + 1 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Search + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + QAbstractItemView::ScrollPerPixel + + + false + + + + 1 + + + + + General + + + generalPage + + + + :/icons/img/config_general.png:/icons/img/config_general.png + + + + + Keyboard shortcuts + + + shortcutsPage + + + + :/icons/img/keyboard.png:/icons/img/keyboard.png + + + + + Look & feel + + + lookAndFeelPage + + + + :/icons/img/config_look_and_feel.png:/icons/img/config_look_and_feel.png + + + + Style + + + stylePage + + + + :/icons/img/config_style.png:/icons/img/config_style.png + + + + + Fonts + + + fontsPage + + + + :/icons/img/config_font.png:/icons/img/config_font.png + + + + + Colors + + + colorsPage + + + + :/icons/img/config_colors.png:/icons/img/config_colors.png + + + + + + Plugins + + + pluginsPage + + + + :/icons/img/plugin.png:/icons/img/plugin.png + + + + Code formatters + + + formatterPluginsPage + + + + + + Data browsing + + + dataBrowsingPage + + + + :/icons/img/table.png:/icons/img/table.png + + + + Data editors + + + dataEditorsPage + + + + :/icons/img/config_data_editors.png:/icons/img/config_data_editors.png + + + + + + + + + + + 5 + 0 + + + + 2 + + + + + + + Data browsing and editing + + + + + + Number of data rows per page: + + + + + + + + 150 + 16777215 + + + + 1 + + + 99999 + + + General.NumberOfRowsPerPage + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + + + 1 + 0 + + + + Data types + + + + + + + + + General.DataEditorsOrder + + + + + + + + + 3 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Available editors: + + + + + + true + + + + + + + + + + Editors selected for this data type: + + + + + + true + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Schema editing + + + + + + + 150 + 16777215 + + + + Number of DDL changes kept in history. + + + 9999999 + + + General.DdlHistorySize + + + + + + + DDL history size: + + + + + + + Don't show DDL preview dialog when commiting schema changes + + + General.DontShowDdlPreview + + + + + + + + + + SQL queries + + + + + + + 150 + 16777215 + + + + Number of queries kept in the history. + + + 999999 + + + General.SqlHistorySize + + + + + + + Number of queries kept in the history. + + + History size: + + + + + + + <p>If there is more than one query in the SQL editor window, then (if this option is enabled) only a single query will be executed - the one under the keyboard insertion cursor. Otherwise all queries will be executed. You can always limit queries to be executed by selecting those queries before calling to execute.</p> + + + Execute only the query under the cursor + + + General.ExecuteCurrentQueryOnly + + + + + + + + + + Updates + + + + + + Automatically check for updates at startup + + + General.CheckUpdatesOnStartup + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter shortcuts by name or key combination + + + + + + + Qt::NoFocus + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::SingleSelection + + + 0 + + + false + + + true + + + false + + + 150 + + + 16 + + + false + + + + Action + + + + + Key combination + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Database list + + + + + + If switched off, then columns will be sorted in the order they are typed in CREATE TABLE statement. + + + Sort table columns alphabetically + + + General.SortColumns + + + + + + + Expand tables node when connected to a database + + + General.ExpandTables + + + + + + + <p>Additional labels are those displayed next to the names on the databases list (they are blue, unless configured otherwise). Enabling this option will result in labels for databases, invalid databases and aggregated nodes (column group, index group, trigger group). For more labels see options below.<p> + + + Display additional labels on the list + + + true + + + false + + + General.ShowDbTreeLabels + + + + + + For regular tables labels will show number of columns, indexes and triggers for each of tables. + + + Display labels for regular tables + + + General.ShowRegularTableLabels + + + + + + + Virtual tables will be marked with a 'virtual' label. + + + Display labels for virtual tables + + + General.ShowVirtualTableLabels + + + + + + + + + + Expand views node when connected to a database + + + General.ExpandViews + + + + + + + If this option is switched off, then objects will be sorted in order they appear in sqlite_master table (which is in order they were created) + + + Sort objects (tables, indexes, triggers and views) alphabetically + + + General.SortObjects + + + + + + + Display system tables and indexes on the list + + + General.ShowSystemObjects + + + + + + + + + + Table windows + + + + + + When enabled, Table Windows will show up with the data tab, instead of the structure tab. + + + Open Table Windows with the data tab for start + + + General.OpenTablesOnData + + + + + + + + + + View windows + + + + + + When enabled, View Windows will show up with the data tab, instead of the structure tab. + + + Open View Windows with the data tab for start + + + General.OpenViewsOnData + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + 0 + + + + + Qt::NoFocus + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + 0 + + + false + + + false + + + 2 + + + false + + + false + + + + 1 + + + + + 2 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Hide built-in plugins + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Current style: + + + + + + + General.Style + + + + + + + + + + Preview + + + + + + 0 + + + + Enabled + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 24 + + + + + + + CheckBox + + + + + + + 50 + + + Qt::Vertical + + + + + + + Qt::Vertical + + + + + + + PushButton + + + + + + + + + + 0 + + + 0 + + + Qt::AlignCenter + + + + + + + + Column + + + + + 123 + + + + 11111 + + + + + 22222 + + + + + 33333 + + + + + + 456 + + + + 44444 + + + + + 55555 + + + + + 66666 + + + + + + + + + ... + + + + + + + + + + RadioButton + + + + + + + + ABC + + + + + XYZ + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;">Abcdefgh</span></p></body></html> + + + + + + + + + + + Disabled + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + false + + + + Language + + + + + Active formatter plugin + + + + + Configuration + + + + + + + + + + + + QFrame::NoFrame + + + 0 + + + true + + + + + 0 + 0 + 258 + 286 + + + + + + + SQL editor font + + + + + + Fonts.SqlEditor + + + + + + + + + + Database list font + + + + + + Fonts.DbTree + + + + + + + + + + Database list additional label font + + + + + + Fonts.DbTreeLabel + + + + + + + + + + Data view font + + + + + + Fonts.DataView + + + + + + + + + + Status field font + + + + + + Fonts.StatusField + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + QFrame::NoFrame + + + 0 + + + true + + + + + 0 + 0 + 307 + 666 + + + + + + + SQL editor colors + + + + + + Current line background + + + + + + + <p>SQL strings are enclosed with single quote characters.</p> + + + String foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorKeywordFg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorStringFg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorCommentFg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorForeground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorCurrentLineBg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorLineNumAreaBg + + + + + + + <p>Bind parameters are placeholders for values yet to be provided by the user. They have one of the forms:</p><ul><li>:param_name</li><li>$param_name</li><li>@param_name</li><li>?</li></ul> + + + Bind parameter foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorParenthesisBg + + + + + + + Highlighted parenthesis background + + + + + + + <p>BLOB values are binary values represented as hexadecimal numbers, like:</p><ul><li>X'12B4'</li><li>x'46A2F4'</li></ul> + + + BLOB value foreground + + + + + + + Regular foreground + + + + + + + Line numbers area background + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorBlobFg + + + + + + + Keyword foreground + + + + + + + Number foreground + + + + + + + Comment foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorNumberFg + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorBindParamFg + + + + + + + <p>Valid objects are name of tables, indexes, triggers, or views that exist in the SQLite database.</p> + + + Valid objects foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.SqlEditorValidObject + + + + + + + + + + Data view colors + + + + + + <p>Any data changes will be outlined with this color, until they're commited to the database.</p> + + + Uncommited data outline color + + + + + + + + 50 + 16777215 + + + + + + + Colors.DataUncommited + + + + + + + <p>In case of error while commiting data changes, the problematic cell will be outlined with this color.</p> + + + Commit error outline color + + + + + + + + 50 + 16777215 + + + + + + + Colors.DataUncommitedError + + + + + + + NULL value foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.DataNullFg + + + + + + + Deleted row background + + + + + + + + 50 + 16777215 + + + + + + + Colors.DataDeletedBg + + + + + + + + + + Database list colors + + + + + + <p>Additional labels are those which tell you SQLite version, number of objects deeper in the tree, etc.</p> + + + Additional labels foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.DbTreeLabelsFg + + + + + + + + + + Status field colors + + + + + + Information message foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.StatusFieldInfoFg + + + + + + + Warning message foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.StatusFieldWarnFg + + + + + + + Error message foreground + + + + + + + + 50 + 16777215 + + + + + + + Colors.StatusFieldErrorFg + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 100 + 30 + + + + + + + + ColorButton + QPushButton +
    common/colorbutton.h
    +
    + + FontEdit + QWidget +
    common/fontedit.h
    + 1 +
    +
    + + categoriesFilter + categoriesTree + pluginsList + buttonBox + expandViewsCheck + sortObjects + sortColumns + ddlHistorySizeSpin + dontShowDdlPreview + queryHistorySizeSpin + checkBox + expandTablesCheck + activeStyleCombo + previewTabs + previewCheckBox + previewVerticalSlider + previewPushButton + previewSpinBox + previewTreeWidget + previewToolButton + previewLineEdit + previewRadioButton + previewComboBox + previewTextEdit + fontsScrollArea + scrollArea + sqlEditorKeywordFgButton + sqlEditorStringFgButton + sqlEditorCommentFgButton + sqlEditorRegularFgButton + sqlEditorCurrLineBgButton + sqlEditorLineNumAreaBgButton + sqlEditorParBgButton + sqlEditorBlobFgButton + sqlEditorNumberFgButton + sqlEditorBindParamFgButton + sqlEditorValidObjectsButton + dataViewUncommitedButton + dataViewErrorButton + dataViewNullFgButton + dataViewDeletedRowBgButton + dbTreeLabelsButton + statusFieldInfoButton + statusFieldWarnButton + statusFieldErrorButton + + + + + + + buttonBox + accepted() + ConfigDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ConfigDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp new file mode 100644 index 0000000..0094ad0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp @@ -0,0 +1,213 @@ +#include "constraintdialog.h" +#include "ui_constraintdialog.h" +#include "iconmanager.h" +#include "constraints/constraintpanel.h" +#include +#include + +ConstraintDialog::ConstraintDialog(Mode mode, SqliteCreateTable::Constraint* constraint, SqliteCreateTable* createTable, Db* db, QWidget* parent) : + QDialog(parent), + ui(new Ui::ConstraintDialog), + mode(mode), + db(db) +{ + ui->setupUi(this); + type = TABLE; + constrStatement = constraint; + this->createTable = createTable; + init(); +} + +ConstraintDialog::ConstraintDialog(Mode mode, SqliteCreateTable::Column::Constraint* constraint, SqliteCreateTable::Column* column, Db* db, QWidget* parent) : + QDialog(parent), + ui(new Ui::ConstraintDialog), + mode(mode), + db(db) +{ + ui->setupUi(this); + type = COLUMN; + constrStatement = constraint; + this->columnStmt = column; + createTable = dynamic_cast(column->parent()); + init(); +} + +ConstraintDialog::~ConstraintDialog() +{ + delete ui; +} + +SqliteStatement* ConstraintDialog::getConstraint() +{ + return constrStatement; +} + +void ConstraintDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void ConstraintDialog::init() +{ + switch (mode) + { + case ConstraintDialog::NEW: + setWindowTitle(tr("New constraint", "constraint dialog")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create", "constraint dialog")); + break; + case ConstraintDialog::EDIT: + setWindowTitle(tr("Edit constraint", "dialog window")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Apply", "constraint dialog")); + break; + } + + connect(this, SIGNAL(accepted()), this, SLOT(storeConfiguration())); + + // Panel object + currentPanel = createConstraintPanel(); + if (!currentPanel) + { + qCritical() << "The constraint panel was not constructed. Probably the constraint type was invalid."; + return; + } + + currentPanel->setDb(db); + currentPanel->setConstraint(constrStatement); + + connect(currentPanel, SIGNAL(updateValidation()), this, SLOT(validate())); + validate(); + + // Put everything in place + updateDefinitionHeader(); + ui->definitionWidget->layout()->addWidget(currentPanel); + + adjustSize(); + currentPanel->setFocus(); +} + +ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint() +{ + switch (type) + { + case ConstraintDialog::TABLE: + return getSelectedConstraint(dynamic_cast(constrStatement)); + case ConstraintDialog::COLUMN: + return getSelectedConstraint(dynamic_cast(constrStatement)); + } + return UNKNOWN; +} + +ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint(SqliteCreateTable::Constraint* constraint) +{ + switch (constraint->type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return PK; + case SqliteCreateTable::Constraint::UNIQUE: + return UNIQUE; + case SqliteCreateTable::Constraint::CHECK: + return CHECK; + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return FK; + case SqliteCreateTable::Constraint::NAME_ONLY: + break; + } + return UNKNOWN; +} + +ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint(SqliteCreateTable::Column::Constraint* constraint) +{ + switch (constraint->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return PK; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return NOTNULL; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return UNIQUE; + case SqliteCreateTable::Column::Constraint::CHECK: + return CHECK; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return DEFAULT; + case SqliteCreateTable::Column::Constraint::COLLATE: + return COLLATE; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return FK; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return UNKNOWN; +} + +ConstraintPanel* ConstraintDialog::createConstraintPanel() +{ + if (!constrStatement) + return nullptr; + + if (type == COLUMN) + return ConstraintPanel::produce(dynamic_cast(constrStatement)); + else + return ConstraintPanel::produce(dynamic_cast(constrStatement)); +} + +void ConstraintDialog::updateDefinitionHeader() +{ + switch (getSelectedConstraint()) + { + case ConstraintDialog::UNKNOWN: + return; + case ConstraintDialog::PK: + ui->titleIcon->setPixmap(ICONS.CONSTRAINT_PRIMARY_KEY); + ui->titleLabel->setText(tr("Primary key", "table constraints")); + break; + case ConstraintDialog::FK: + ui->titleIcon->setPixmap(ICONS.CONSTRAINT_FOREIGN_KEY); + ui->titleLabel->setText(tr("Foreign key", "table constraints")); + break; + case ConstraintDialog::UNIQUE: + ui->titleIcon->setPixmap(ICONS.CONSTRAINT_UNIQUE); + ui->titleLabel->setText(tr("Unique", "table constraints")); + break; + case ConstraintDialog::NOTNULL: + ui->titleIcon->setPixmap(ICONS.CONSTRAINT_NOT_NULL); + ui->titleLabel->setText(tr("Not NULL", "table constraints")); + break; + case ConstraintDialog::CHECK: + ui->titleIcon->setPixmap(ICONS.CONSTRAINT_CHECK); + ui->titleLabel->setText(tr("Check", "table constraints")); + break; + case ConstraintDialog::COLLATE: + ui->titleIcon->setPixmap(ICONS.CONSTRAINT_COLLATION); + ui->titleLabel->setText(tr("Collate", "table constraints")); + break; + case ConstraintDialog::DEFAULT: + ui->titleIcon->setPixmap(ICONS.CONSTRAINT_DEFAULT); + ui->titleLabel->setText(tr("Default", "table constraints")); + break; + } +} + +void ConstraintDialog::validate() +{ + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(currentPanel->validate()); +} + +void ConstraintDialog::storeConfiguration() +{ + if (!currentPanel) + { + qWarning() << "Called to store constraint configuration, but there's no current panel."; + return; + } + + currentPanel->storeDefinition(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h new file mode 100644 index 0000000..fe24c0f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h @@ -0,0 +1,79 @@ +#ifndef CONSTRAINTDIALOG_H +#define CONSTRAINTDIALOG_H + +#include "parser/ast/sqlitecreatetable.h" +#include "db/db.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +namespace Ui { + class ConstraintDialog; +} + +class ConstraintPanel; + +class GUI_API_EXPORT ConstraintDialog : public QDialog +{ + Q_OBJECT + + public: + enum Mode + { + NEW, + EDIT + }; + + enum Constraint + { + PK, + FK, + UNIQUE, + NOTNULL, + CHECK, + COLLATE, + DEFAULT, + UNKNOWN + }; + + enum Type + { + TABLE, + COLUMN + }; + + explicit ConstraintDialog(Mode mode, SqliteCreateTable::Constraint* constraint, SqliteCreateTable* createTable, Db* db, + QWidget *parent = 0); + explicit ConstraintDialog(Mode mode, SqliteCreateTable::Column::Constraint* constraint, SqliteCreateTable::Column* column, Db* db, + QWidget *parent = 0); + ~ConstraintDialog(); + + SqliteStatement* getConstraint(); + + protected: + void changeEvent(QEvent *e); + + private: + void init(); + Constraint getSelectedConstraint(); + Constraint getSelectedConstraint(SqliteCreateTable::Constraint* constraint); + Constraint getSelectedConstraint(SqliteCreateTable::Column::Constraint* constraint); + ConstraintPanel* createConstraintPanel(); + void updateDefinitionHeader(); + + Ui::ConstraintDialog *ui = nullptr; + Type type; + Mode mode; + Db* db = nullptr; + SqliteStatement* constrStatement = nullptr; + QPointer createTable; + QPointer columnStmt; + QHash panels; + ConstraintPanel* currentPanel = nullptr; + + private slots: + void validate(); + void storeConfiguration(); +}; + +#endif // CONSTRAINTDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui new file mode 100644 index 0000000..7df34d8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui @@ -0,0 +1,113 @@ + + + ConstraintDialog + + + + 0 + 0 + 400 + 300 + + + + + 400 + 0 + + + + Dialog + + + + + + + + + + 18 + 16777215 + + + + + + + + + + + + 75 + true + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ConstraintDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ConstraintDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp new file mode 100644 index 0000000..c94546e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp @@ -0,0 +1,219 @@ +#include "dbconverterdialog.h" +#include "ui_dbconverterdialog.h" +#include "common/global.h" +#include "dblistmodel.h" +#include "db/db.h" +#include "common/utils_sql.h" +#include "dbversionconverter.h" +#include "services/dbmanager.h" +#include "iconmanager.h" +#include "uiutils.h" +#include "versionconvertsummarydialog.h" +#include "mainwindow.h" +#include "errorsconfirmdialog.h" +#include "parser/ast/sqlitecreatetable.h" +#include "services/pluginmanager.h" +#include "plugins/dbplugin.h" +#include "db/sqlquery.h" +#include "services/notifymanager.h" +#include "common/widgetcover.h" +#include +#include +#include + +DbConverterDialog::DbConverterDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::DbConverterDialog) +{ + init(); +} + +DbConverterDialog::~DbConverterDialog() +{ + delete ui; + safe_delete(converter); +} + +void DbConverterDialog::setDb(Db* db) +{ + ui->srcDbCombo->setCurrentText(db->getName()); + srcDb = db; + srcDbChanged(); +} + +void DbConverterDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + setWindowTitle(tr("Convert database")); + + widgetCover = new WidgetCover(this); + widgetCover->setVisible(false); + widgetCover->initWithInterruptContainer(); + + ui->trgFileButton->setIcon(ICONS.OPEN_FILE); + + converter = new DbVersionConverter(); + + dbListModel = new DbListModel(this); + ui->srcDbCombo->setModel(dbListModel); + + connect(ui->srcDbCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(srcDbChanged(int))); + connect(ui->trgVersionCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState())); + connect(ui->trgFileEdit, SIGNAL(textChanged(QString)), this, SLOT(updateState())); + connect(ui->trgNameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateState())); + connect(converter, SIGNAL(conversionFailed(QString)), this, SLOT(processingFailed(QString))); + connect(converter, SIGNAL(conversionSuccessful()), this, SLOT(processingSuccessful())); + connect(converter, SIGNAL(conversionAborted()), this, SLOT(processingAborted())); + connect(widgetCover, SIGNAL(cancelClicked()), converter, SLOT(interrupt())); +} + +void DbConverterDialog::srcDbChanged() +{ + dontUpdateState = true; + ui->srcDbVersionCombo->clear(); + ui->trgVersionCombo->clear(); + if (srcDb) + { + // Source version + QList dialects = converter->getSupportedVersions(); + QStringList versionNames = converter->getSupportedVersionNames(); + Dialect dialect = srcDb->getDialect(); + int idx = dialects.indexOf(dialect); + QString type = versionNames[idx]; + ui->srcDbVersionCombo->addItem(type); + ui->srcDbVersionCombo->setCurrentText(type); + + // Target version + QString oldTrgVersion = ui->trgVersionCombo->currentText(); + versionNames.removeAt(idx); + ui->trgVersionCombo->addItems(versionNames); + if (versionNames.contains(oldTrgVersion)) + ui->trgVersionCombo->setCurrentText(oldTrgVersion); + else if (versionNames.size() > 0) + ui->trgVersionCombo->setCurrentIndex(0); + + // File + QString trgFile = srcDb->getPath() + "_new"; + int i = 0; + while (QFileInfo(trgFile).exists()) + { + trgFile = srcDb->getPath() + "_new" + QString::number(i++); + } + + ui->trgFileEdit->setText(trgFile); + + // Name + QString generatedName = generateUniqueName(srcDb->getName() + "_new", DBLIST->getDbNames()); + ui->trgNameEdit->setText(generatedName); + } + else + { + ui->srcDbVersionCombo->setCurrentText(""); + ui->trgFileEdit->setText(""); + ui->trgVersionCombo->setCurrentText(""); + ui->trgNameEdit->setText(""); + } + dontUpdateState = false; + updateState(); +} + +bool DbConverterDialog::validate() +{ + bool srcDbOk = (srcDb != nullptr); + setValidState(ui->srcDbCombo, srcDbOk, tr("Select source database")); + + QString dstDbPath = ui->trgFileEdit->text(); + QFileInfo dstDbFi(dstDbPath); + bool dstDbOk = (!dstDbFi.exists() || dstDbFi.isWritable()) && dstDbFi != QFileInfo(srcDb->getPath()); + bool dstExists = dstDbFi.exists(); + setValidState(ui->trgFileEdit, dstDbOk, tr("Enter valid and writable file path.")); + if (dstExists && dstDbOk) + setValidStateInfo(ui->trgFileEdit, tr("Entered file exists and will be overwritten.")); + + QString name = ui->trgNameEdit->text(); + bool nameOk = !name.isEmpty() && !DBLIST->getDbNames().contains(name); + setValidState(ui->trgNameEdit, nameOk, tr("Enter a not empty, unique name (as in the list of databases on the left).")); + + bool dstDialectOk = ui->trgVersionCombo->currentIndex() > -1; + QString msg; + if (!dstDialectOk && ui->trgVersionCombo->count() == 0) + msg = tr("No valid target dialect available. Conversion not possible."); + else + msg = tr("Select valid target dialect."); + + setValidState(ui->trgVersionCombo, dstDialectOk, msg); + + return (srcDbOk && nameOk && dstDbOk && dstDialectOk); +} + +void DbConverterDialog::accept() +{ + if (!validate()) + return; + + QStringList versionNames = converter->getSupportedVersionNames(); + QList dialects = converter->getSupportedVersions(); + QString trgDialectName = ui->trgVersionCombo->currentText(); + int idx = versionNames.indexOf(trgDialectName); + if (idx == -1) + { + qCritical() << "Could not find target dialect on list of supported dialects in DbConverterDialog::accept()"; + return; + } + + Dialect srcDialect = srcDb->getDialect(); + Dialect trgDialect = dialects[idx]; + QString trgFile = ui->trgFileEdit->text(); + QString trgName = ui->trgNameEdit->text(); + widgetCover->show(); + converter->convert(srcDialect, trgDialect, srcDb, trgFile, trgName, &DbConverterDialog::confirmConversion, &DbConverterDialog::confirmConversionErrors); +} + +void DbConverterDialog::srcDbChanged(int index) +{ + srcDb = dbListModel->getDb(index); + srcDbChanged(); +} + +void DbConverterDialog::updateState() +{ + if (dontUpdateState) + return; + + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate()); +} + +void DbConverterDialog::processingFailed(const QString& errorMessage) +{ + widgetCover->hide(); + notifyError(errorMessage); +} + +void DbConverterDialog::processingSuccessful() +{ + notifyInfo(tr("Database %1 has been successfully converted and now is available under new name: %2").arg(srcDb->getName(), ui->trgNameEdit->text())); + QDialog::accept(); +} + +void DbConverterDialog::processingAborted() +{ + widgetCover->hide(); +} + +bool DbConverterDialog::confirmConversion(const QList >& diffs) +{ + VersionConvertSummaryDialog dialog(MAINWINDOW); + dialog.setWindowTitle(tr("SQL statements conversion")); + dialog.setSides(diffs); + return dialog.exec() == QDialog::Accepted; +} + +bool DbConverterDialog::confirmConversionErrors(const QSet& errors) +{ + ErrorsConfirmDialog dialog(MAINWINDOW); + dialog.setTopLabel(tr("Following error occurred while converting SQL statements to the target SQLite version:")); + dialog.setBottomLabel(tr("Would you like to ignore those errors and proceed?")); + dialog.setErrors(errors); + return dialog.exec() == QDialog::Accepted; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h new file mode 100644 index 0000000..4267a1b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h @@ -0,0 +1,52 @@ +#ifndef DBCONVERTERDIALOG_H +#define DBCONVERTERDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +class DbListModel; +class Db; +class DbVersionConverter; +class WidgetCover; + +namespace Ui { + class DbConverterDialog; +} + +class GUI_API_EXPORT DbConverterDialog : public QDialog +{ + Q_OBJECT + + public: + explicit DbConverterDialog(QWidget *parent = 0); + ~DbConverterDialog(); + + void setDb(Db* db); + + private: + void init(); + void srcDbChanged(); + bool validate(); + + static bool confirmConversion(const QList >& diffs); + static bool confirmConversionErrors(const QSet& errors); + + Ui::DbConverterDialog *ui = nullptr; + DbListModel* dbListModel = nullptr; + Db* srcDb = nullptr; + DbVersionConverter* converter = nullptr; + bool dontUpdateState = false; + WidgetCover* widgetCover = nullptr; + + public slots: + void accept(); + + private slots: + void srcDbChanged(int index); + void updateState(); + void processingFailed(const QString& errorMessage); + void processingSuccessful(); + void processingAborted(); +}; + +#endif // DBCONVERTERDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui new file mode 100644 index 0000000..d328e99 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui @@ -0,0 +1,144 @@ + + + DbConverterDialog + + + + 0 + 0 + 400 + 251 + + + + Dialog + + + + + + Source database + + + + + + + + + Source database version: + + + + + + + false + + + + + + + + + + Target database + + + + + + Target version: + + + + + + + This is the file that will be created as a result of the conversion. + + + + + + + Target file: + + + + + + + Name of the new database: + + + + + + + + + + + + + + + + + This is the name that the converted database will be added to SQLiteStudio with. + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DbConverterDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DbConverterDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp new file mode 100644 index 0000000..c9a7f28 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp @@ -0,0 +1,590 @@ +#include "dbdialog.h" +#include "ui_dbdialog.h" +#include "services/pluginmanager.h" +#include "plugins/dbplugin.h" +#include "uiutils.h" +#include "common/utils.h" +#include "uiconfig.h" +#include "services/dbmanager.h" +#include "common/global.h" +#include "iconmanager.h" +#include "common/unused.h" +#include +#include +#include +#include +#include +#include + +DbDialog::DbDialog(Mode mode, QWidget *parent) : + QDialog(parent), + ui(new Ui::DbDialog), + mode(mode) +{ + init(); +} + +DbDialog::~DbDialog() +{ + delete ui; +} + +void DbDialog::setDb(Db* db) +{ + this->db = db; +} + +void DbDialog::setPermanent(bool perm) +{ + ui->permamentCheckBox->setChecked(perm); +} + +QString DbDialog::getPath() +{ + return ui->fileEdit->text(); +} + +void DbDialog::setPath(const QString& path) +{ + ui->fileEdit->setText(path); +} + +QString DbDialog::getName() +{ + return ui->nameEdit->text(); +} + +Db* DbDialog::getDb() +{ + if (ui->typeCombo->currentIndex() < 0) + return nullptr; + + Db* testDb = nullptr; + QHash options = collectOptions(); + QString path = ui->fileEdit->text(); + foreach (DbPlugin* plugin, dbPlugins) + { + if (options.contains(DB_PLUGIN) && options[DB_PLUGIN].toString() != plugin->getName()) + continue; + + testDb = plugin->getInstance("", path, options); + if (testDb) + return testDb; + } + return testDb; +} + +bool DbDialog::isPermanent() +{ + return ui->permamentCheckBox->isChecked(); +} + +void DbDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void DbDialog::showEvent(QShowEvent *e) +{ + if (db) + { + int idx = ui->typeCombo->findText(db->getTypeLabel()); + ui->typeCombo->setCurrentIndex(idx); + ui->typeCombo->setEnabled(false); // converting to other type is in separate dialog, it's different feature + + ui->generateCheckBox->setChecked(false); + ui->fileEdit->setText(db->getPath()); + ui->nameEdit->setText(db->getName()); + } + else if (ui->typeCombo->count() > 0) + { + int idx = ui->typeCombo->findText("SQLite3"); // we should have SQLite3 plugin + if (idx > -1) + ui->typeCombo->setCurrentIndex(idx); + else + ui->typeCombo->setCurrentIndex(0); + } + + existingDatabaseNames = DBLIST->getDbNames(); + if (mode == EDIT) + existingDatabaseNames.removeOne(db->getName()); + + updateOptions(); + updateState(); + + QDialog::showEvent(e); +} + +void DbDialog::init() +{ + ui->setupUi(this); + + ui->browseButton->setIcon(ICONS.DATABASE_FILE); + dbPlugins = PLUGINS->getLoadedPlugins(); + foreach (DbPlugin* dbPlugin, dbPlugins) + { + ui->typeCombo->addItem(dbPlugin->getLabel()); + } + + ui->browseButton->setVisible(true); + ui->testConnIcon->setVisible(false); + + connect(ui->fileEdit, SIGNAL(textChanged(QString)), this, SLOT(fileChanged(QString))); + connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameModified(QString))); + connect(ui->generateCheckBox, SIGNAL(toggled(bool)), this, SLOT(generateNameSwitched(bool))); + connect(ui->browseButton, SIGNAL(clicked()), this, SLOT(browseClicked())); + connect(ui->testConnButton, SIGNAL(clicked()), this, SLOT(testConnectionClicked())); + connect(ui->typeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(dbTypeChanged(int))); + + generateNameSwitched(true); +} + +void DbDialog::updateOptions() +{ + setUpdatesEnabled(false); + + // Remove olds + foreach (QWidget* w, optionWidgets) + { + ui->gridLayout->removeWidget(w); + delete w; + } + adjustSize(); + + optionWidgets.clear(); + optionKeyToWidget.clear(); + optionKeyToType.clear(); + helperToKey.clear(); + + lastWidgetInTabOrder = ui->permamentCheckBox; + + // Retrieve new list + DbPlugin* plugin = nullptr; + if (dbPlugins.count() > 0) + { + int idx = ui->typeCombo->currentIndex(); + if (idx > -1 ) + { + plugin = dbPlugins[idx]; + QList optList = plugin->getOptionsList(); + if (optList.size() > 0) + { + // Add new options + int row = ADDITIONAL_ROWS_BEGIN_INDEX; + foreach (DbPluginOption opt, optList) + addOption(opt, row++); + } + } + } + + adjustSize(); + setUpdatesEnabled(true); +} + +void DbDialog::addOption(const DbPluginOption& option, int row) +{ + QLabel* label = new QLabel(option.label, this); + QWidget* editor = nullptr; + QWidget* editorHelper = nullptr; // TODO, based on plugins for Url handlers + + editor = getEditor(option, editorHelper); + Q_ASSERT(editor != nullptr); + + if (!option.toolTip.isNull()) + editor->setToolTip(option.toolTip); + + optionWidgets << label << editor; + + optionKeyToWidget[option.key] = editor; + optionKeyToType[option.key] = option.type; + ui->gridLayout->addWidget(label, row, 0); + ui->gridLayout->addWidget(editor, row, 1); + + setTabOrder(lastWidgetInTabOrder, editor); + lastWidgetInTabOrder = editor; + + if (editorHelper) + { + ui->gridLayout->addWidget(editorHelper, row, 2); + optionWidgets << editorHelper; + helperToKey[editorHelper] = option.key; + + setTabOrder(lastWidgetInTabOrder, editorHelper); + lastWidgetInTabOrder = editorHelper; + } + + if (db && db->getConnectionOptions().contains(option.key)) + setValueFor(option.type, editor, db->getConnectionOptions()[option.key]); +} + +QWidget *DbDialog::getEditor(const DbPluginOption& opt, QWidget*& editorHelper) +{ + QWidget* editor = nullptr; + QLineEdit* le = nullptr; + editorHelper = nullptr; + switch (opt.type) + { + case DbPluginOption::STRING: + { + editor = new QLineEdit(this); + le = dynamic_cast(editor); + connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged())); + break; + } + case DbPluginOption::PASSWORD: + { + editor = new QLineEdit(this); + le = dynamic_cast(editor); + le->setEchoMode(QLineEdit::Password); + connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged())); + break; + } + case DbPluginOption::CHOICE: + { + QComboBox* cb = new QComboBox(this); + editor = cb; + cb->setEditable(!opt.choiceReadOnly); + cb->addItems(opt.choiceValues); + cb->setCurrentText(opt.defaultValue.toString()); + connect(cb, SIGNAL(currentIndexChanged(QString)), this, SLOT(propertyChanged())); + break; + } + case DbPluginOption::INT: + { + QSpinBox* sb = new QSpinBox(this); + editor = sb; + if (!opt.minValue.isNull()) + sb->setMinimum(opt.minValue.toInt()); + + if (!opt.maxValue.isNull()) + sb->setMaximum(opt.maxValue.toInt()); + + if (!opt.defaultValue.isNull()) + sb->setValue(opt.defaultValue.toInt()); + + connect(sb, SIGNAL(valueChanged(int)), this, SLOT(propertyChanged())); + break; + } + case DbPluginOption::FILE: + { + editor = new QLineEdit(this); + le = dynamic_cast(editor); + editorHelper = new QPushButton(tr("Browse"), this); + connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged())); + connect(editorHelper, SIGNAL(pressed()), this, SLOT(browseForFile())); + break; + } + case DbPluginOption::BOOL: + { + QCheckBox* cb = new QCheckBox(this); + editor = cb; + if (!opt.defaultValue.isNull()) + cb->setChecked(opt.defaultValue.toBool()); + + connect(cb, SIGNAL(stateChanged(int)), this, SLOT(propertyChanged())); + break; + } + case DbPluginOption::DOUBLE: + { + QDoubleSpinBox* sb = new QDoubleSpinBox(this); + editor = sb; + if (!opt.minValue.isNull()) + sb->setMinimum(opt.minValue.toDouble()); + + if (!opt.maxValue.isNull()) + sb->setMaximum(opt.maxValue.toDouble()); + + if (!opt.defaultValue.isNull()) + sb->setValue(opt.defaultValue.toDouble()); + + connect(sb, SIGNAL(valueChanged(double)), this, SLOT(propertyChanged())); + break; + } + default: + // TODO plugin based handling of custom editors + qWarning() << "Unhandled DbDialog option for creating editor."; + break; + } + + if (le) + { + le->setPlaceholderText(opt.placeholderText); + le->setText(opt.defaultValue.toString()); + } + + return editor; +} + +QVariant DbDialog::getValueFrom(DbPluginOption::Type type, QWidget *editor) +{ + QVariant value; + switch (type) + { + case DbPluginOption::STRING: + case DbPluginOption::PASSWORD: + case DbPluginOption::FILE: + value = dynamic_cast(editor)->text(); + break; + case DbPluginOption::INT: + value = dynamic_cast(editor)->value(); + break; + case DbPluginOption::BOOL: + value = dynamic_cast(editor)->isChecked(); + break; + case DbPluginOption::DOUBLE: + value = dynamic_cast(editor)->value(); + break; + case DbPluginOption::CHOICE: + value = dynamic_cast(editor)->currentText(); + break; + default: + // TODO plugin based handling of custom editors + qWarning() << "Unhandled DbDialog option for value."; + break; + } + return value; +} + +void DbDialog::setValueFor(DbPluginOption::Type type, QWidget *editor, const QVariant &value) +{ + switch (type) + { + case DbPluginOption::STRING: + case DbPluginOption::FILE: + case DbPluginOption::PASSWORD: + dynamic_cast(editor)->setText(value.toString()); + break; + case DbPluginOption::INT: + dynamic_cast(editor)->setValue(value.toInt()); + break; + case DbPluginOption::BOOL: + dynamic_cast(editor)->setChecked(value.toBool()); + break; + case DbPluginOption::DOUBLE: + dynamic_cast(editor)->setValue(value.toDouble()); + break; + case DbPluginOption::CHOICE: + dynamic_cast(editor)->setCurrentText(value.toString()); + break; + default: + qWarning() << "Unhandled DbDialog option to set value."; + // TODO plugin based handling of custom editors + break; + } +} + +void DbDialog::updateType() +{ + QFileInfo file(ui->fileEdit->text()); + if (!file.exists() || file.isDir()) + { + ui->typeCombo->setEnabled(true); + return; + } + + DbPlugin* validPlugin = nullptr; + QHash options; + QString path = ui->fileEdit->text(); + Db* probeDb = nullptr; + foreach (DbPlugin* plugin, dbPlugins) + { + probeDb = plugin->getInstance("", path, options); + if (probeDb) + { + delete probeDb; + probeDb = nullptr; + + validPlugin = plugin; + break; + } + } + + if (validPlugin) + ui->typeCombo->setCurrentText(validPlugin->getLabel()); + + ui->typeCombo->setEnabled(!validPlugin); +} + +QHash DbDialog::collectOptions() +{ + QHash options; + if (ui->typeCombo->currentIndex() < 0) + return options; + + for (const QString key : optionKeyToWidget.keys()) + options[key] = getValueFrom(optionKeyToType[key], optionKeyToWidget[key]); + + DbPlugin* plugin = nullptr; + if (dbPlugins.count() > 0) + { + plugin = dbPlugins[ui->typeCombo->currentIndex()]; + options[DB_PLUGIN] = plugin->getName(); + } + + return options; +} + +bool DbDialog::testDatabase() +{ + QString path = ui->fileEdit->text(); + bool existed = QFile::exists(path); + bool res = getDb() != nullptr; + if (!existed) + { + QFile file(path); + file.remove(); + } + return res; +} + +bool DbDialog::validate() +{ + if (ui->fileEdit->text().isEmpty()) + return false; + + if (ui->nameEdit->text().isEmpty()) + return false; + + if (ui->typeCombo->count() == 0) + return false; + + if (ui->typeCombo->currentIndex() < 0) + return false; + + return true; +} + +void DbDialog::updateState() +{ + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate()); +} + +void DbDialog::propertyChanged() +{ + ui->testConnIcon->setVisible(false); +} + +void DbDialog::typeChanged(int index) +{ + UNUSED(index); + updateOptions(); + updateState(); +} + +void DbDialog::valueForNameGenerationChanged() +{ + if (!ui->generateCheckBox->isChecked()) + { + updateState(); + return; + } + + DbPlugin* plugin = nullptr; + if (dbPlugins.count() > 0) + { + plugin = dbPlugins[ui->typeCombo->currentIndex()]; + QString generatedName = plugin->generateDbName(ui->fileEdit->text()); + generatedName = generateUniqueName(generatedName, existingDatabaseNames); + ui->nameEdit->setText(generatedName); + } +} + +void DbDialog::browseForFile() +{ + QString dir = getFileDialogInitPath(); + QString path = QFileDialog::getOpenFileName(0, QString(), dir); + if (path.isNull()) + return; + + QString key = helperToKey[dynamic_cast(sender())]; + setValueFor(optionKeyToType[key], optionKeyToWidget[key], path); + + setFileDialogInitPathByFile(path); +} + +void DbDialog::generateNameSwitched(bool checked) +{ + if (checked) + { + ui->nameEdit->setPlaceholderText(tr("The name will be auto-generated")); + valueForNameGenerationChanged(); + } + else + { + ui->nameEdit->setPlaceholderText(tr("Type the name")); + } + + ui->nameEdit->setReadOnly(checked); +} + +void DbDialog::fileChanged(const QString &arg1) +{ + UNUSED(arg1); + valueForNameGenerationChanged(); + updateType(); + propertyChanged(); +} + +void DbDialog::browseClicked() +{ + QFileInfo fileInfo(ui->fileEdit->text()); + QString dir; + if (ui->fileEdit->text().isEmpty()) + dir = getFileDialogInitPath(); + else if (fileInfo.exists() && fileInfo.isFile()) + dir = fileInfo.absolutePath(); + else if (fileInfo.dir().exists()) + dir = fileInfo.dir().absolutePath(); + else + dir = getFileDialogInitPath(); + + QString path = getDbPath(dir); + if (path.isNull()) + return; + + setFileDialogInitPathByFile(path); + + ui->fileEdit->setText(path); + updateState(); +} + +void DbDialog::testConnectionClicked() +{ + ui->testConnIcon->setPixmap(testDatabase() ? ICONS.TEST_CONN_OK : ICONS.TEST_CONN_ERROR); + ui->testConnIcon->setVisible(true); +} + +void DbDialog::dbTypeChanged(int index) +{ + typeChanged(index); + propertyChanged(); +} + +void DbDialog::nameModified(const QString &arg1) +{ + UNUSED(arg1); + updateState(); +} + +void DbDialog::accept() +{ + QString name = getName(); + QString path = getPath(); + QHash options = collectOptions(); + bool perm = isPermanent(); + bool result; + if (mode == ADD) + result = DBLIST->addDb(name, path, options, perm); + else + result = DBLIST->updateDb(db, name, path, options, perm); + + if (result) + QDialog::accept(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h new file mode 100644 index 0000000..b2c0d68 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h @@ -0,0 +1,89 @@ +#ifndef DBDIALOG_H +#define DBDIALOG_H + +#include "db/db.h" +#include "db/dbpluginoption.h" +#include "guiSQLiteStudio_global.h" +#include +#include +#include +#include + +class DbPlugin; +class QGridLayout; +struct DbPluginOption; + +namespace Ui { + class DbDialog; +} + +class GUI_API_EXPORT DbDialog : public QDialog +{ + Q_OBJECT + + public: + enum Mode + { + ADD, + EDIT + }; + + DbDialog(Mode mode, QWidget *parent = 0); + ~DbDialog(); + + void setDb(Db* db); + void setPermanent(bool perm); + + QString getPath(); + void setPath(const QString& path); + QString getName(); + QHash collectOptions(); + bool isPermanent(); + + protected: + void changeEvent(QEvent *e); + void showEvent(QShowEvent* e); + + private: + void init(); + void updateOptions(); + void addOption(const DbPluginOption& option, int row); + QWidget* getEditor(const DbPluginOption& opt, QWidget *&editorHelper); + QVariant getValueFrom(DbPluginOption::Type type, QWidget* editor); + void setValueFor(DbPluginOption::Type type, QWidget* editor, const QVariant& value); + void updateType(); + Db* getDb(); + bool testDatabase(); + bool validate(); + void updateState(); + + Ui::DbDialog *ui = nullptr; + Mode mode; + QStringList existingDatabaseNames; + Db* db = nullptr; + QList dbPlugins; + QList optionWidgets; + QHash optionKeyToWidget; + QHash optionKeyToType; + QHash helperToKey; + QWidget* lastWidgetInTabOrder = nullptr; + + static const constexpr int ADDITIONAL_ROWS_BEGIN_INDEX = 4; + + private slots: + void typeChanged(int index); + void valueForNameGenerationChanged(); + void browseForFile(); + void generateNameSwitched(bool checked); + void fileChanged(const QString &arg1); + void browseClicked(); + void testConnectionClicked(); + void propertyChanged(); + void dbTypeChanged(int index); + void nameModified(const QString &arg1); + + public slots: + void accept(); +}; + +#endif // DBDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui new file mode 100644 index 0000000..fb53428 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui @@ -0,0 +1,236 @@ + + + DbDialog + + + + 0 + 0 + 455 + 200 + + + + + 450 + 0 + + + + Database + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Database driver + + + + + + + + + true + + + + + + + Name + + + + + + + Type + + + + + + + + + + + + Browse for database file on local computer + + + + + + + + + + + + File + + + + + + + Generate name basing on file path + + + + + + true + + + + + + + Permanent + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <p>Enable this if you want the database to be stored in configuration file and restored every time SQLiteStudio is started.</p> + + + + + + true + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Test database connection + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + + + fileEdit + browseButton + nameEdit + generateCheckBox + typeCombo + permamentCheckBox + + + + + buttonBox + accepted() + DbDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DbDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp new file mode 100644 index 0000000..e86f9cd --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp @@ -0,0 +1,58 @@ +#include "ddlpreviewdialog.h" +#include "ui_ddlpreviewdialog.h" +#include "services/codeformatter.h" +#include "uiconfig.h" +#include "sqlitestudio.h" +#include "db/db.h" + +DdlPreviewDialog::DdlPreviewDialog(Db* db, QWidget *parent) : + QDialog(parent), + ui(new Ui::DdlPreviewDialog), + db(db) +{ + ui->setupUi(this); +} + +DdlPreviewDialog::~DdlPreviewDialog() +{ + delete ui; +} + +void DdlPreviewDialog::setDdl(const QString& ddl) +{ + QString formatted = SQLITESTUDIO->getCodeFormatter()->format("sql", ddl, db); + ui->ddlEdit->setPlainText(formatted); +} + +void DdlPreviewDialog::setDdl(const QStringList& ddlList) +{ + QStringList fixedList; + QString newDdl; + foreach (const QString& ddl, ddlList) + { + newDdl = ddl.trimmed(); + if (!newDdl.endsWith(";")) + newDdl.append(";"); + + fixedList << SQLITESTUDIO->getCodeFormatter()->format("sql", newDdl, db); + } + setDdl(fixedList.join("\n")); +} + +void DdlPreviewDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void DdlPreviewDialog::accept() +{ + CFG_UI.General.DontShowDdlPreview.set(ui->dontShowAgainCheck->isChecked()); + QDialog::accept(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h new file mode 100644 index 0000000..403405f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h @@ -0,0 +1,35 @@ +#ifndef DDLPREVIEWDIALOG_H +#define DDLPREVIEWDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +class Db; + +namespace Ui { + class DdlPreviewDialog; +} + +class GUI_API_EXPORT DdlPreviewDialog : public QDialog +{ + Q_OBJECT + + public: + explicit DdlPreviewDialog(Db* db, QWidget *parent = 0); + ~DdlPreviewDialog(); + + void setDdl(const QString& ddl); + void setDdl(const QStringList& ddlList); + + protected: + void changeEvent(QEvent *e); + + private: + Ui::DdlPreviewDialog *ui = nullptr; + Db* db = nullptr; + + public slots: + void accept(); +}; + +#endif // DDLPREVIEWDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui new file mode 100644 index 0000000..8c3678a --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui @@ -0,0 +1,106 @@ + + + DdlPreviewDialog + + + + 0 + 0 + 527 + 351 + + + + Queries to be executed + + + + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Don't show again + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    +
    + + + + buttonBox + accepted() + DdlPreviewDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DdlPreviewDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp new file mode 100644 index 0000000..c0a73f3 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp @@ -0,0 +1,47 @@ +#include "errorsconfirmdialog.h" +#include "ui_errorsconfirmdialog.h" +#include "iconmanager.h" + +ErrorsConfirmDialog::ErrorsConfirmDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ErrorsConfirmDialog) +{ + ui->setupUi(this); +} + +ErrorsConfirmDialog::~ErrorsConfirmDialog() +{ + delete ui; +} + +void ErrorsConfirmDialog::setErrors(const QHash>& errors) +{ + ui->list->clear(); + + for (const QString& key : errors.keys()) + { + for (const QString& err : errors[key]) + ui->list->addItem(QString("[%1] %2").arg(key, err)); + } + + for (int i = 0, total = ui->list->count(); i < total; ++i) + ui->list->item(i)->setIcon(ICONS.STATUS_ERROR); +} + +void ErrorsConfirmDialog::setErrors(const QSet& errors) +{ + ui->list->clear(); + ui->list->addItems(errors.toList()); + for (int i = 0, total = ui->list->count(); i < total; ++i) + ui->list->item(i)->setIcon(ICONS.STATUS_ERROR); +} + +void ErrorsConfirmDialog::setTopLabel(const QString& text) +{ + ui->topLabel->setText(text); +} + +void ErrorsConfirmDialog::setBottomLabel(const QString& text) +{ + ui->bottomLabel->setText(text); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h new file mode 100644 index 0000000..3e71d7b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h @@ -0,0 +1,28 @@ +#ifndef ERRORSCONFIRMDIALOG_H +#define ERRORSCONFIRMDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class ErrorsConfirmDialog; +} + +class GUI_API_EXPORT ErrorsConfirmDialog : public QDialog +{ + Q_OBJECT + + public: + explicit ErrorsConfirmDialog(QWidget *parent = 0); + ~ErrorsConfirmDialog(); + + void setErrors(const QHash >& errors); + void setErrors(const QSet& errors); + void setTopLabel(const QString& text); + void setBottomLabel(const QString& text); + + private: + Ui::ErrorsConfirmDialog *ui = nullptr; +}; + +#endif // ERRORSCONFIRMDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui new file mode 100644 index 0000000..81cdb17 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui @@ -0,0 +1,85 @@ + + + ErrorsConfirmDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + Following errors occured: + + + + + + + true + + + + + + + Would you like to proceed? + + + + + + + Qt::Horizontal + + + QDialogButtonBox::No|QDialogButtonBox::Yes + + + + + + + + + buttonBox + accepted() + ErrorsConfirmDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ErrorsConfirmDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp new file mode 100644 index 0000000..e495dd9 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp @@ -0,0 +1,737 @@ +#include "exportdialog.h" +#include "ui_exportdialog.h" +#include "dblistmodel.h" +#include "dbobjlistmodel.h" +#include "services/dbmanager.h" +#include "uiutils.h" +#include "services/pluginmanager.h" +#include "formmanager.h" +#include "plugins/exportplugin.h" +#include "configmapper.h" +#include "selectabledbobjmodel.h" +#include "dbtree/dbtree.h" +#include "dbtree/dbtreemodel.h" +#include "schemaresolver.h" +#include "common/widgetcover.h" +#include "services/notifymanager.h" +#include "uiconfig.h" +#include +#include +#include +#include +#include +#include +#include + +ExportDialog::ExportDialog(QWidget *parent) : + QWizard(parent), + ui(new Ui::ExportDialog) +{ + init(); +} + +ExportDialog::~ExportDialog() +{ + EXPORT_MANAGER->interrupt(); + safe_delete(configMapper); + delete ui; +} + +void ExportDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + +#ifdef Q_OS_MACX + resize(width() + 150, height()); + setPixmap(QWizard::BackgroundPixmap, addOpacity(ICONS.DATABASE_EXPORT_WIZARD.toQIcon().pixmap(800, 800), 0.3)); +#endif + + widgetCover = new WidgetCover(this); + widgetCover->initWithInterruptContainer(tr("Cancel")); + connect(widgetCover, SIGNAL(cancelClicked()), EXPORT_MANAGER, SLOT(interrupt())); + widgetCover->setVisible(false); + + initPageOrder(); + + initModePage(); + initTablePage(); + initFormatPage(); + initQueryPage(); + initDbObjectsPage(); + + connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(pageChanged(int))); + connect(EXPORT_MANAGER, SIGNAL(exportSuccessful()), this, SLOT(success())); + connect(EXPORT_MANAGER, SIGNAL(exportFinished()), this, SLOT(hideCoverWidget())); + connect(EXPORT_MANAGER, SIGNAL(storeInClipboard(QByteArray, QString)), this, SLOT(storeInClipboard(QByteArray, QString))); + connect(EXPORT_MANAGER, SIGNAL(storeInClipboard(QString)), this, SLOT(storeInClipboard(QString))); + connect(EXPORT_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(handleValidationResultFromPlugin(bool,CfgEntry*,QString))); + connect(EXPORT_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool))); +} + +void ExportDialog::setTableMode(Db* db, const QString& table) +{ + if (!db->isOpen()) + { + qWarning() << "Cannot export from closed database."; + return; + } + + setStartId(pageId(ui->tablePage)); + exportMode = ExportManager::TABLE; + this->db = db; + this->table = table; + + ui->exportTableDbNameCombo->addItem(db->getName()); + ui->exportTableDbNameCombo->setCurrentText(db->getName()); + ui->exportTableDbNameCombo->setEnabled(false); + ui->exportTableNameCombo->addItem(table); + ui->exportTableNameCombo->setCurrentText(table); + ui->exportTableNameCombo->setEnabled(false); +} + +void ExportDialog::setQueryMode(Db* db, const QString& query) +{ + if (!db->isOpen()) + { + qWarning() << "Cannot export from closed database."; + return; + } + + setStartId(pageId(ui->queryPage)); + exportMode = ExportManager::QUERY_RESULTS; + this->db = db; + this->query = query; + + ui->queryDatabaseCombo->addItem(db->getName()); + ui->queryDatabaseCombo->setCurrentText(db->getName()); + ui->queryDatabaseCombo->setEnabled(false); + ui->queryEdit->setPlainText(query); + updateQueryEditDb(); + ui->queryEdit->checkSyntaxNow(); +} + +void ExportDialog::setDatabaseMode(Db* db) +{ + if (!db->isOpen()) + { + qWarning() << "Cannot export from closed database."; + return; + } + + setStartId(pageId(ui->databaseObjectsPage)); + exportMode = ExportManager::DATABASE; + this->db = db; +} + +void ExportDialog::initModePage() +{ + connect(ui->subjectDatabaseRadio, SIGNAL(clicked()), this, SLOT(updateExportMode())); + connect(ui->subjectTableRadio, SIGNAL(clicked()), this, SLOT(updateExportMode())); + connect(ui->subjectQueryRadio, SIGNAL(clicked()), this, SLOT(updateExportMode())); +} + +void ExportDialog::initTablePage() +{ + ui->tablePage->setValidator([=]() -> bool + { + bool dbOk = ui->exportTableDbNameCombo->currentIndex() > -1; + bool tableOk = ui->exportTableNameCombo->currentIndex() > -1; + + setValidState(ui->exportTableDbNameCombo, dbOk, tr("Select database to export.")); + setValidState(ui->exportTableNameCombo, tableOk, tr("Select table to export.")); + + return dbOk && tableOk; + }); + + dbListModel = new DbListModel(this); + dbListModel->setCombo(ui->exportTableDbNameCombo); + dbListModel->setSortMode(DbListModel::SortMode::Alphabetical); + + tablesModel = new DbObjListModel(this); + tablesModel->setType(DbObjListModel::ObjectType::TABLE); + + connect(this, SIGNAL(tablePageCompleteChanged()), ui->tablePage, SIGNAL(completeChanged())); +} + +void ExportDialog::initQueryPage() +{ + ui->queryPage->setValidator([=]() -> bool + { + bool queryOk = !ui->queryEdit->toPlainText().trimmed().isEmpty(); + queryOk &= ui->queryEdit->isSyntaxChecked() && !ui->queryEdit->haveErrors(); + bool dbOk = ui->queryDatabaseCombo->currentIndex() > -1; + + setValidState(ui->queryDatabaseCombo, dbOk, tr("Select database to export.")); + setValidState(ui->queryEdit, queryOk, tr("Enter valid query to export.")); + + return dbOk && queryOk; + }); + + connect(ui->queryEdit, SIGNAL(errorsChecked(bool)), ui->queryPage, SIGNAL(completeChanged())); + connect(ui->queryEdit, SIGNAL(textChanged()), ui->queryPage, SIGNAL(completeChanged())); + connect(ui->queryDatabaseCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateQueryEditDb())); + connect(this, SIGNAL(queryPageCompleteChanged()), ui->queryPage, SIGNAL(completeChanged())); +} + +void ExportDialog::initDbObjectsPage() +{ + selectableDbListModel = new SelectableDbObjModel(this); + selectableDbListModel->setSourceModel(DBTREE->getModel()); + ui->dbObjectsTree->setModel(selectableDbListModel); + + ui->databaseObjectsPage->setValidator([=]() -> bool + { + bool dbOk = ui->dbObjectsDatabaseCombo->currentIndex() > -1; + bool listOk = selectableDbListModel->getCheckedObjects().size() > 0; + + setValidState(ui->dbObjectsDatabaseCombo, dbOk, tr("Select database to export.")); + setValidState(ui->dbObjectsTree, listOk, tr("Select at least one object to export.")); + + return listOk; + }); + + connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateDbObjTree())); + connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(QString)), ui->databaseObjectsPage, SIGNAL(completeChanged())); + connect(selectableDbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->databaseObjectsPage, SIGNAL(completeChanged())); + connect(ui->objectsSelectAllButton, SIGNAL(clicked()), this, SLOT(dbObjectsSelectAll())); + connect(ui->objectsDeselectAllButton, SIGNAL(clicked()), this, SLOT(dbObjectsDeselectAll())); +} + +void ExportDialog::initFormatPage() +{ + ui->formatAndOptionsPage->setValidator([=]() -> bool + { + setValidState(ui->exportFileEdit, true); + bool outputFileSupported = currentPlugin && currentPlugin->getSupportedModes().testFlag(ExportManager::FILE); + if (outputFileSupported && ui->exportFileRadio->isChecked()) + { + QString path = ui->exportFileEdit->text(); + if (path.trimmed().isEmpty()) + { + setValidState(ui->exportFileEdit, false, tr("You must provide a file name to export to.")); + return false; + } + + QDir dir(path); + if (dir.exists() && QFileInfo(path).isDir()) + { + setValidState(ui->exportFileEdit, false, tr("Path you provided is an existing directory. You cannot overwrite it.")); + return false; + } + + if (!dir.cdUp()) + { + setValidState(ui->exportFileEdit, false, tr("The directory '%1' does not exist.").arg(dir.dirName())); + return false; + } + + QFileInfo fi(path); + if (fi.exists()) + setValidStateInfo(ui->exportFileEdit, tr("The file '%1' exists and will be overwritten.").arg(fi.fileName())); + } + return ui->formatCombo->currentIndex() > -1 && ui->encodingCombo->currentIndex() > -1 && isPluginConfigValid(); + }); + + ui->exportFileButton->setIcon(ICONS.EXPORT_FILE_BROWSE); + connect(ui->exportFileButton, SIGNAL(clicked()), this, SLOT(browseForExportFile())); + + connect(ui->formatCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(pluginSelected())); + connect(ui->formatCombo, SIGNAL(currentTextChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged())); + connect(ui->encodingCombo, SIGNAL(currentTextChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged())); + connect(ui->exportFileEdit, SIGNAL(textChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged())); + connect(ui->exportFileRadio, SIGNAL(clicked()), ui->formatAndOptionsPage, SIGNAL(completeChanged())); + connect(ui->exportClipboardRadio, SIGNAL(clicked()), ui->formatAndOptionsPage, SIGNAL(completeChanged())); + connect(this, SIGNAL(formatPageCompleteChanged()), ui->formatAndOptionsPage, SIGNAL(completeChanged())); + connect(ui->exportFileRadio, SIGNAL(clicked()), this, SLOT(updateOptions())); + connect(ui->exportClipboardRadio, SIGNAL(clicked()), this, SLOT(updateOptions())); + connect(ui->exportFileRadio, SIGNAL(clicked()), this, SLOT(updateExportOutputOptions())); + connect(ui->exportClipboardRadio, SIGNAL(clicked()), this, SLOT(updateExportOutputOptions())); +} + +int ExportDialog::nextId() const +{ + if (exportMode == ExportManager::UNDEFINED) + return pageId(ui->proxyPage); + + QList order = pageOrder[exportMode]; + + int idx = order.indexOf(currentPage()); + idx++; + if (idx < order.size()) + return pageId(order[idx]); + + return -1; +} + +bool ExportDialog::isPluginConfigValid() const +{ + return pluginConfigOk.size() == 0; +} + +void ExportDialog::initPageOrder() +{ + setStartId(pageId(ui->exportSubjectPage)); + pageOrder[ExportManager::DATABASE] = {ui->databaseObjectsPage, ui->formatAndOptionsPage}; + pageOrder[ExportManager::TABLE] = {ui->tablePage, ui->formatAndOptionsPage}; + pageOrder[ExportManager::QUERY_RESULTS] = {ui->queryPage, ui->formatAndOptionsPage}; + updateExportMode(); +} + +int ExportDialog::pageId(QWizardPage* wizardPage) const +{ + for (int id : pageIds()) + { + if (page(id) == wizardPage) + return id; + } + return -1; +} + +void ExportDialog::tablePageDisplayed() +{ + if (!tablePageVisited) + { + if (table.isNull()) // table mode selected by user, not forced by setTableMode(). + { + ui->exportTableDbNameCombo->setModel(dbListModel); + connect(ui->exportTableDbNameCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDbTables())); + + ui->exportTableNameCombo->setModel(tablesModel); + connect(ui->exportTableNameCombo, SIGNAL(currentTextChanged(QString)), ui->tablePage, SIGNAL(completeChanged())); + } + updateDbTables(); + emit tablePageCompleteChanged(); + tablePageVisited = true; + } +} + +void ExportDialog::queryPageDisplayed() +{ + if (!queryPageVisited) + { + if (query.isNull()) // query mode selected by user, not forced by setQueryMode(). + { + ui->queryDatabaseCombo->setModel(dbListModel); + connect(ui->queryDatabaseCombo, SIGNAL(currentIndexChanged(int)), ui->queryPage, SIGNAL(completeChanged())); + } + + updateQueryEditDb(); + emit queryPageCompleteChanged(); + queryPageVisited = true; + } +} + +void ExportDialog::dbObjectsPageDisplayed() +{ + if (!dbObjectsPageVisited) + { + ui->dbObjectsDatabaseCombo->setModel(dbListModel); + connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(int)), ui->queryPage, SIGNAL(completeChanged())); + + if (db) + ui->dbObjectsDatabaseCombo->setCurrentText(db->getName()); + + dbObjectsPageVisited = true; + } +} + +void ExportDialog::formatPageDisplayed() +{ + if (!formatPageVisited) + { + ui->formatCombo->addItems(EXPORT_MANAGER->getAvailableFormats(exportMode)); + + ui->encodingCombo->addItems(textCodecNames()); + ui->encodingCombo->setCurrentText(defaultCodecName()); + + formatPageVisited = true; + } + pluginSelected(); +} + +ExportPlugin* ExportDialog::getSelectedPlugin() const +{ + return EXPORT_MANAGER->getPluginForFormat(ui->formatCombo->currentText()); +} + +void ExportDialog::updateExportMode() +{ + if (ui->subjectDatabaseRadio->isChecked()) + exportMode = ExportManager::DATABASE; + else if (ui->subjectTableRadio->isChecked()) + exportMode = ExportManager::TABLE; + else if (ui->subjectQueryRadio->isChecked()) + exportMode = ExportManager::QUERY_RESULTS; + else + exportMode = ExportManager::UNDEFINED; +} + +void ExportDialog::pageChanged(int pageId) +{ + QWizardPage* wizardPage = page(pageId); + if (wizardPage == ui->tablePage) + tablePageDisplayed(); + else if (wizardPage == ui->queryPage) + queryPageDisplayed(); + else if (wizardPage == ui->databaseObjectsPage) + dbObjectsPageDisplayed(); + else if (wizardPage == ui->formatAndOptionsPage) + formatPageDisplayed(); + else if (wizardPage == ui->proxyPage) + next(); +} + +void ExportDialog::updateDbTables() +{ + if (!table.isNull()) + return; // we don't want tables to be automatically updated if this is strictly set table + + QString dbName = ui->exportTableDbNameCombo->currentText(); + db = DBLIST->getByName(dbName); + + tablesModel->setDb(db); +} + +void ExportDialog::browseForExportFile() +{ + QStringList filters; + if (currentPlugin) + filters << currentPlugin->getFormatName()+" (*." + currentPlugin->defaultFileExtension() + ")"; + + filters << tr("All files (*)"); + + QString dir = getFileDialogInitPath(); + QString fileName = QFileDialog::getSaveFileName(this, tr("Pick file to export to"), dir, filters.join(";;"), 0, QFileDialog::DontConfirmOverwrite); + if (fileName.isNull()) + return; + + if (currentPlugin && !fileName.endsWith("." + currentPlugin->defaultFileExtension())) + fileName += "." + currentPlugin->defaultFileExtension(); + + ui->exportFileEdit->setText(fileName); + setFileDialogInitPathByFile(fileName); +} + +void ExportDialog::pluginSelected() +{ + pluginConfigOk.clear(); + + currentPlugin = getSelectedPlugin(); + if (!currentPlugin) + { + qCritical() << "Could not find export plugin, while it was selected on ui:" << ui->formatCombo->currentText(); + return; + } + + currentPlugin->setExportMode(exportMode); + + updateExportOutputOptions(); + updateOptions(); + + if (currentPlugin->getConfig() && !currentPlugin->getConfig()->isPersistable()) + currentPlugin->getConfig()->reset(); +} + +void ExportDialog::updateExportOutputOptions() +{ + ExportManager::StandardConfigFlags options = currentPlugin->standardOptionsToEnable(); + bool displayCodec = options.testFlag(ExportManager::CODEC) && !ui->exportClipboardRadio->isChecked(); + bool clipboardSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::CLIPBOARD); + bool outputFileSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::FILE); + + bool enabled = outputFileSupported && ui->exportFileRadio->isChecked(); + ui->exportFileEdit->setEnabled(enabled); + ui->exportFileButton->setEnabled(enabled); + + ui->exportClipboardRadio->setVisible(clipboardSupported); + ui->exportFileRadio->setVisible(outputFileSupported); + ui->exportFileEdit->setVisible(outputFileSupported); + ui->exportFileButton->setVisible(outputFileSupported); + if (!clipboardSupported && outputFileSupported) + ui->exportFileRadio->setChecked(true); + + ui->encodingCombo->setVisible(displayCodec); + ui->encodingLabel->setVisible(displayCodec); + if (displayCodec) + { + QString codec = currentPlugin->getDefaultEncoding(); + int idx = ui->encodingCombo->findText(codec); + if (idx > -1) + ui->encodingCombo->setCurrentIndex(idx); + } + + ui->exportToGroup->setVisible(clipboardSupported || outputFileSupported || displayCodec); +} + +void ExportDialog::updateQueryEditDb() +{ + Db* db = getDbForExport(ui->queryDatabaseCombo->currentText()); + ui->queryEdit->setDb(db); +} + +void ExportDialog::updateOptions() +{ + ui->optionsGroup->setVisible(false); + + if (!currentPlugin) + { + qCritical() << "Could not find export plugin, while it was selected on ui:" << ui->formatCombo->currentText(); + return; + } + + int optionsRow = 0; + updatePluginOptions(currentPlugin, optionsRow); + ui->optionsGroup->setVisible(optionsRow > 0); +} + +void ExportDialog::updateDbObjTree() +{ + selectableDbListModel->setDbName(ui->dbObjectsDatabaseCombo->currentText()); + + QModelIndex root = selectableDbListModel->index(0, 0); + if (root.isValid()) + { + root = setupNewDbObjTreeRoot(root); + ui->dbObjectsTree->setRootIndex(root); + + ui->dbObjectsTree->expand(root); + QModelIndex child; + for (int i = 0; (child = root.child(i, 0)).isValid(); i++) + ui->dbObjectsTree->expand(child); + } + dbObjectsSelectAll(); +} + +void ExportDialog::dbObjectsSelectAll() +{ + selectableDbListModel->setRootChecked(true); +} + +void ExportDialog::dbObjectsDeselectAll() +{ + selectableDbListModel->setRootChecked(false); +} + +void ExportDialog::hideCoverWidget() +{ + widgetCover->hide(); +} + +void ExportDialog::storeInClipboard(const QByteArray& bytes, const QString& mimeType) +{ + QMimeData* mimeData = new QMimeData; + mimeData->setData(mimeType, bytes); + QApplication::clipboard()->setMimeData(mimeData); +} + +void ExportDialog::storeInClipboard(const QString& str) +{ + QApplication::clipboard()->setText(str); +} + +void ExportDialog::success() +{ + QWizard::accept(); +} + +void ExportDialog::accept() +{ + doExport(); +} + +void ExportDialog::updatePluginOptions(ExportPlugin* plugin, int& optionsRow) +{ + safe_delete(pluginOptionsWidget); + + QString formName = plugin->getExportConfigFormName(); + CfgMain* cfgMain = plugin->getConfig(); + if (formName.isNull() || !cfgMain) + { + if (!formName.isNull()) + { + qWarning() << "FormName is given, but cfgMain is null in ExportDialog::updatePluginOptions() for plugin:" << plugin->getName() + << ", formName:" << formName; + } + return; + } + + if (!FORMS->hasWidget(formName)) + { + qWarning() << "Export plugin" << plugin->getName() << "requested for form named" << formName << "but FormManager doesn't have it." + << "Available forms are:" << FORMS->getAvailableForms(); + return; + } + + safe_delete(configMapper); + + QGridLayout* grid = dynamic_cast(ui->optionsGroup->layout()); + + pluginOptionsWidget = FORMS->createWidget(formName); + + if (pluginOptionsWidget->layout()) + pluginOptionsWidget->layout()->setMargin(0); + + grid->addWidget(pluginOptionsWidget, 1, 0, 1, 2); + optionsRow++; + + configMapper = new ConfigMapper(cfgMain); + configMapper->bindToConfig(pluginOptionsWidget); + connect(configMapper, SIGNAL(modified()), this, SLOT(updateValidation())); + plugin->validateOptions(); +} + +void ExportDialog::updateValidation() +{ + if (!currentPlugin) + return; + + currentPlugin->validateOptions(); + emit formatPageCompleteChanged(); +} + +void ExportDialog::doExport() +{ + widgetCover->show(); + + ExportManager::StandardExportConfig stdConfig = getExportConfig(); + QString format = ui->formatCombo->currentText(); + switch (exportMode) + { + case ExportManager::DATABASE: + exportDatabase(stdConfig, format); + break; + case ExportManager::TABLE: + exportTable(stdConfig, format); + break; + case ExportManager::QUERY_RESULTS: + exportQuery(stdConfig, format); + break; + case ExportManager::UNDEFINED: + qCritical() << "Finished export dialog with undefined mode."; + notifyInternalError(); + break; + case ExportManager::FILE: + case ExportManager::CLIPBOARD: + break; + } +} + +void ExportDialog::exportDatabase(const ExportManager::StandardExportConfig& stdConfig, const QString& format) +{ + Db* db = getDbForExport(ui->dbObjectsDatabaseCombo->currentText()); + if (!db || !db->isValid()) + return; + + EXPORT_MANAGER->configure(format, stdConfig); + EXPORT_MANAGER->exportDatabase(db, selectableDbListModel->getCheckedObjects()); +} + +void ExportDialog::exportTable(const ExportManager::StandardExportConfig& stdConfig, const QString& format) +{ + Db* db = getDbForExport(ui->exportTableDbNameCombo->currentText()); + if (!db || !db->isValid()) + return; + + EXPORT_MANAGER->configure(format, stdConfig); + // TODO when dbnames are fully supported, pass the dbname below + EXPORT_MANAGER->exportTable(db, QString::null, ui->exportTableNameCombo->currentText()); +} + +void ExportDialog::exportQuery(const ExportManager::StandardExportConfig& stdConfig, const QString& format) +{ + Db* db = getDbForExport(ui->queryDatabaseCombo->currentText()); + if (!db || !db->isValid()) + return; + + EXPORT_MANAGER->configure(format, stdConfig); + EXPORT_MANAGER->exportQueryResults(db, ui->queryEdit->toPlainText()); +} + +ExportManager::StandardExportConfig ExportDialog::getExportConfig() const +{ + bool clipboardSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::CLIPBOARD); + bool outputFileSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::FILE); + bool clipboard = clipboardSupported && ui->exportClipboardRadio->isChecked(); + + ExportManager::StandardExportConfig stdConfig; + stdConfig.intoClipboard = clipboard; + + if (clipboard) + stdConfig.outputFileName = QString::null; + else if (outputFileSupported) + stdConfig.outputFileName = ui->exportFileEdit->text(); + + if (exportMode == ExportManager::DATABASE) + stdConfig.exportData = ui->exportDbDataCheck->isChecked(); + else if (exportMode == ExportManager::TABLE) + stdConfig.exportData = ui->exportTableDataCheck->isChecked(); + else + stdConfig.exportData = false; + + if (ui->encodingCombo->isVisible() && ui->encodingCombo->currentIndex() > -1) + stdConfig.codec = ui->encodingCombo->currentText(); + else + stdConfig.codec = defaultCodecName(); + + return stdConfig; +} + +Db* ExportDialog::getDbForExport(const QString& name) +{ + Db* db = DBLIST->getByName(name); + if (!db) + { + qCritical() << "Could not find db selected in combo:" << name; + notifyInternalError(); + return nullptr; + } + return db; +} + +void ExportDialog::notifyInternalError() +{ + notifyError(tr("Internal error during export. This is a bug. Please report it.")); +} + +QModelIndex ExportDialog::setupNewDbObjTreeRoot(const QModelIndex& root) +{ + QModelIndex newRoot = root; + DbTreeItem* item = nullptr; + while (newRoot.isValid()) + { + item = selectableDbListModel->getItemForIndex(newRoot); + if (item->getType() == DbTreeItem::Type::DB) + return newRoot; + + newRoot = newRoot.child(0, 0); + } + return newRoot; +} + +void ExportDialog::handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg) +{ + QWidget* w = configMapper->getBindWidgetForConfig(key); + if (w) + setValidState(w, valid, errorMsg); + + if (valid == pluginConfigOk.contains(key)) // if state changed + { + if (!valid) + pluginConfigOk[key] = false; + else + pluginConfigOk.remove(key); + + emit formatPageCompleteChanged(); + } +} + +void ExportDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled) +{ + QWidget* w = configMapper->getBindWidgetForConfig(key); + if (!w) + return; + + w->setVisible(visible); + w->setEnabled(enabled); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h new file mode 100644 index 0000000..296aa4d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h @@ -0,0 +1,105 @@ +#ifndef EXPORTDIALOG_H +#define EXPORTDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include "services/exportmanager.h" +#include + +namespace Ui { + class ExportDialog; +} + +class DbListModel; +class DbObjListModel; +class SelectableDbObjModel; +class WidgetCover; +class ConfigMapper; + +class GUI_API_EXPORT ExportDialog : public QWizard +{ + Q_OBJECT + + public: + explicit ExportDialog(QWidget *parent = 0); + ~ExportDialog(); + + void setTableMode(Db* db, const QString& table); + void setQueryMode(Db* db, const QString& query); + void setDatabaseMode(Db* db); + int nextId() const; + bool isPluginConfigValid() const; + + private: + void init(); + void initModePage(); + void initTablePage(); + void initQueryPage(); + void initDbObjectsPage(); + void initFormatPage(); + void initPageOrder(); + int pageId(QWizardPage* wizardPage) const; + void tablePageDisplayed(); + void queryPageDisplayed(); + void dbObjectsPageDisplayed(); + void formatPageDisplayed(); + ExportPlugin* getSelectedPlugin() const; + void updatePluginOptions(ExportPlugin* plugin, int& optionsRow); + void doExport(); + void exportDatabase(const ExportManager::StandardExportConfig& stdConfig, const QString& format); + void exportTable(const ExportManager::StandardExportConfig& stdConfig, const QString& format); + void exportQuery(const ExportManager::StandardExportConfig& stdConfig, const QString& format); + ExportManager::StandardExportConfig getExportConfig() const; + Db* getDbForExport(const QString& name); + void notifyInternalError(); + QModelIndex setupNewDbObjTreeRoot(const QModelIndex& root); + + QHash> pageOrder; + + Ui::ExportDialog *ui = nullptr; + ExportManager::ExportMode exportMode = ExportManager::UNDEFINED; + Db* db = nullptr; + QString query; + QString table; + DbListModel* dbListModel = nullptr; + DbObjListModel* tablesModel = nullptr; + SelectableDbObjModel* selectableDbListModel = nullptr; + QWidget* pluginOptionsWidget = nullptr; + bool tablePageVisited = false; + bool queryPageVisited = false; + bool dbObjectsPageVisited = false; + bool formatPageVisited = false; + WidgetCover* widgetCover = nullptr; + ConfigMapper* configMapper = nullptr; + QHash pluginConfigOk; + ExportPlugin* currentPlugin = nullptr; + + private slots: + void handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg); + void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled); + void updateExportMode(); + void pageChanged(int pageId); + void updateDbTables(); + void browseForExportFile(); + void pluginSelected(); + void updateExportOutputOptions(); + void updateQueryEditDb(); + void updateOptions(); + void updateDbObjTree(); + void dbObjectsSelectAll(); + void dbObjectsDeselectAll(); + void hideCoverWidget(); + void storeInClipboard(const QByteArray& bytes, const QString& mimeType); + void storeInClipboard(const QString& str); + void success(); + void updateValidation(); + + public slots: + void accept(); + + signals: + void formatPageCompleteChanged(); + void tablePageCompleteChanged(); + void queryPageCompleteChanged(); +}; + +#endif // EXPORTDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui new file mode 100644 index 0000000..9f84232 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui @@ -0,0 +1,438 @@ + + + ExportDialog + + + + 0 + 0 + 515 + 414 + + + + Export dialog + + + QWizard::CancelButtonOnLeft|QWizard::NoDefaultButton + + + + What do you want to export? + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + A database + + + true + + + + + + + A single table + + + + + + + Query results + + + + + + + + + + + + Table to export + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Database + + + + + + + Table + + + + + + + . + + + + + + + + + + Options + + + + + + When this option is unchecked, then only table DDL (CREATE TABLE statement) is exported. + + + Export table data + + + true + + + + + + + Export table indexes + + + true + + + + + + + Export table triggers + + + true + + + + + + + Qt::Horizontal + + + + + + + + true + + + + Note, that exporting table indexes and triggers may be unsupported by some output formats. + + + true + + + + + + + + + + + Select database objects to export + + + + + + false + + + + + + + Export data from tables + + + true + + + + + + + Select all + + + + + + + Deselect all + + + + + + + + + + Database: + + + + + + + + Query to export results for + + + + + + + + + + + + Database: + + + + + + + Query to be executed for results: + + + + + + + + Export format and options + + + + + + #formatScrollArea { background: transparent; } + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 298 + 288 + + + + #formatScrollAreaContents { background: transparent; } + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Export format + + + + + + + + + + + + Output + + + + + + Exported file path + + + + + + + Clipboard + + + + + + + ... + + + + + + + File + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Exported text encoding: + + + + + + + + + + + + + + + + Export format options + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + VerifiableWizardPage + QWizardPage +
    common/verifiablewizardpage.h
    + 1 +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    +
    + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp new file mode 100644 index 0000000..32ec30f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp @@ -0,0 +1,376 @@ +#include "importdialog.h" +#include "dblistmodel.h" +#include "dbobjlistmodel.h" +#include "common/widgetstateindicator.h" +#include "uiutils.h" +#include "common/widgetcover.h" +#include "services/dbmanager.h" +#include "services/pluginmanager.h" +#include "services/importmanager.h" +#include "sqlitestudio.h" +#include "plugins/importplugin.h" +#include "ui_importdialog.h" +#include "configmapper.h" +#include "formmanager.h" +#include "common/utils.h" +#include "uiconfig.h" +#include +#include +#include + +ImportDialog::ImportDialog(QWidget *parent) : + QWizard(parent), + ui(new Ui::ImportDialog) +{ + init(); +} + +ImportDialog::~ImportDialog() +{ + IMPORT_MANAGER->interrupt(); + safe_delete(configMapper); + delete ui; +} + +void ImportDialog::setDbAndTable(Db* db, const QString& table) +{ + if (!db) + return; + + ui->dbNameCombo->setCurrentText(db->getName()); + ui->tableNameCombo->setCurrentText(table); +} + +void ImportDialog::setDb(Db* db) +{ + if (!db) + return; + + ui->dbNameCombo->setCurrentText(db->getName()); +} + +bool ImportDialog::isPluginConfigValid() const +{ + return pluginConfigOk.size() == 0; +} + +void ImportDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + +#ifdef Q_OS_MACX + resize(width() + 150, height()); + setPixmap(QWizard::BackgroundPixmap, addOpacity(ICONS.DATABASE_IMPORT_WIZARD.toQIcon().pixmap(800, 800), 0.3)); +#endif + + initTablePage(); + initDataSourcePage(); + + widgetCover = new WidgetCover(this); + widgetCover->initWithInterruptContainer(tr("Cancel")); + connect(widgetCover, SIGNAL(cancelClicked()), IMPORT_MANAGER, SLOT(interrupt())); + widgetCover->setVisible(false); + + connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(pageChanged())); + connect(IMPORT_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(handleValidationResultFromPlugin(bool,CfgEntry*,QString))); + connect(IMPORT_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool))); + connect(IMPORT_MANAGER, SIGNAL(importSuccessful()), this, SLOT(success())); + connect(IMPORT_MANAGER, SIGNAL(importFinished()), this, SLOT(hideCoverWidget())); +} + +void ImportDialog::initTablePage() +{ + dbListModel = new DbListModel(this); + dbListModel->setCombo(ui->dbNameCombo); + dbListModel->setSortMode(DbListModel::SortMode::Alphabetical); + ui->dbNameCombo->setModel(dbListModel); + + tablesModel = new DbObjListModel(this); + tablesModel->setIncludeSystemObjects(false); + tablesModel->setType(DbObjListModel::ObjectType::TABLE); + ui->tableNameCombo->setModel(tablesModel); + refreshTables(); + + connect(ui->dbNameCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshTables())); + connect(ui->tableNameCombo, SIGNAL(currentTextChanged(QString)), ui->tablePage, SIGNAL(completeChanged())); + + ui->tablePage->setValidator([=]() -> bool + { + bool valid = !ui->tableNameCombo->currentText().isEmpty(); + setValidStateWihtTooltip(ui->tableNameCombo, tr("If you type table name that doesn't exist, it will be created."), valid, tr("Enter the table name")); + return valid; + }); +} + +void ImportDialog::initDataSourcePage() +{ + ui->inputFileButton->setIcon(ICONS.OPEN_FILE); + connect(ui->inputFileButton, SIGNAL(clicked()), this, SLOT(browseForInputFile())); + + ui->codecCombo->addItems(textCodecNames()); + ui->codecCombo->setCurrentText(defaultCodecName()); + + ui->dsPage->setValidator([=]() -> bool + { + setValidState(ui->dsTypeCombo, true); + if (!currentPlugin) + { + setValidState(ui->dsTypeCombo, false, tr("Select import plugin.")); + return false; + } + + if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME)) + { + QString path = ui->inputFileEdit->text(); + if (path.trimmed().isEmpty()) + { + setValidState(ui->inputFileEdit, false, tr("You must provide a file to import from.")); + return false; + } + + QFileInfo file(path); + if (!file.exists()) + { + setValidState(ui->inputFileEdit, false, tr("The file '%1' does not exist.").arg(path)); + return false; + } + + if (file.exists() && file.isDir()) + { + setValidState(ui->inputFileEdit, false, tr("Path you provided is a directory. A regular file is required.")); + return false; + } + setValidState(ui->inputFileEdit, true); + } + return ui->dsTypeCombo->currentIndex() > -1 && ui->codecCombo->currentIndex() > -1 && isPluginConfigValid(); + }); + + connect(this, SIGNAL(dsPageCompleteChanged()), ui->dsPage, SIGNAL(completeChanged())); + connect(ui->dsTypeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(pluginSelected())); + connect(ui->dsTypeCombo, SIGNAL(currentTextChanged(QString)), ui->dsPage, SIGNAL(completeChanged())); + connect(ui->codecCombo, SIGNAL(currentTextChanged(QString)), ui->dsPage, SIGNAL(completeChanged())); + connect(ui->inputFileEdit, SIGNAL(textChanged(QString)), ui->dsPage, SIGNAL(completeChanged())); + + ui->dsTypeCombo->addItems(IMPORT_MANAGER->getImportDataSourceTypes()); +} + +void ImportDialog::removeOldOptions() +{ + safe_delete(configMapper); + safe_delete(pluginOptionsWidget); +} + +void ImportDialog::updateStandardOptions() +{ + bool showFileName = currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME); + bool showCodec = currentPlugin->standardOptionsToEnable().testFlag(ImportManager::CODEC); + + if (!showFileName && !showCodec) + { + ui->dsOptionsGroup->setVisible(false); + return; + } + + ui->dsOptionsGroup->setVisible(true); + + int row = 0; + QGridLayout* grid = dynamic_cast(ui->dsOptionsGroup->layout()); + if (showFileName) + { + grid->addWidget(ui->inputFileLabel, row, 0); + grid->addWidget(ui->inputFileWidget, row, 1); + row++; + } + else + { + grid->removeWidget(ui->inputFileLabel); + grid->removeWidget(ui->inputFileWidget); + } + + ui->inputFileLabel->setVisible(showFileName); + ui->inputFileWidget->setVisible(showFileName); + + if (showCodec) + { + grid->addWidget(ui->codecLabel, row, 0); + grid->addWidget(ui->codecCombo, row, 1); + row++; + } + else + { + grid->removeWidget(ui->codecLabel); + grid->removeWidget(ui->codecCombo); + } + + ui->codecLabel->setVisible(showCodec); + ui->codecCombo->setVisible(showCodec); +} + +void ImportDialog::updatePluginOptions(int& rows) +{ + QString formName = currentPlugin->getImportConfigFormName(); + CfgMain* cfgMain = currentPlugin->getConfig(); + ui->dsPluginOptionsGroup->setVisible(false); + if (formName.isNull() || !cfgMain) + { + if (!formName.isNull()) + { + qWarning() << "FormName is given, but cfgMain is null in ImportDialog::updatePluginOptions() for plugin:" << currentPlugin->getName() + << ", formName:" << formName; + } + return; + } + + if (!FORMS->hasWidget(formName)) + { + qWarning() << "Import plugin" << currentPlugin->getName() << "requested for form named" << formName << "but FormManager doesn't have it." + << "Available forms are:" << FORMS->getAvailableForms(); + return; + } + + pluginOptionsWidget = FORMS->createWidget(formName); + if (!pluginOptionsWidget) + { + qWarning() << "Import plugin" << currentPlugin->getName() << "requested for form named" << formName << "but FormManager returned null."; + return; + } + + ui->dsPluginOptionsGroup->setVisible(true); + + if (pluginOptionsWidget->layout()) + pluginOptionsWidget->layout()->setMargin(0); + + ui->dsPluginOptionsGroup->layout()->addWidget(pluginOptionsWidget); + rows++; + + configMapper = new ConfigMapper(cfgMain); + configMapper->bindToConfig(pluginOptionsWidget); + connect(configMapper, SIGNAL(modified()), this, SLOT(updateValidation())); + updateValidation(); +} + +void ImportDialog::handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg) +{ + QWidget* w = configMapper->getBindWidgetForConfig(key); + if (w) + setValidState(w, valid, errorMsg); + + if (valid == pluginConfigOk.contains(key)) // if state changed + { + if (!valid) + pluginConfigOk[key] = false; + else + pluginConfigOk.remove(key); + } +} + +void ImportDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled) +{ + QWidget* w = configMapper->getBindWidgetForConfig(key); + if (!w) + return; + + w->setVisible(visible); + w->setEnabled(enabled); +} + +void ImportDialog::refreshTables() +{ + Db* db = DBLIST->getByName(ui->dbNameCombo->currentText()); + if (db) + tablesModel->setDb(db); +} + +void ImportDialog::pluginSelected() +{ + ui->dsPluginOptionsGroup->setVisible(false); + removeOldOptions(); + currentPlugin = IMPORT_MANAGER->getPluginForDataSourceType(ui->dsTypeCombo->currentText()); + if (!currentPlugin) + return; + + updateStandardOptions(); + + int rows = 0; + updatePluginOptions(rows); + ui->dsPluginOptionsGroup->setVisible(rows > 0); +} + +void ImportDialog::updateValidation() +{ + if (!currentPlugin) + return; + + currentPlugin->validateOptions(); + emit dsPageCompleteChanged(); +} + +void ImportDialog::pageChanged() +{ + if (currentPage() == ui->dsPage) + updateValidation(); +} + +void ImportDialog::browseForInputFile() +{ + if (!currentPlugin) + { + qCritical() << "Called ImportDialog::browseForInputFile(), but no ImportPlugin is selected."; + return; + } + + QString dir = getFileDialogInitPath(); + QString filter = currentPlugin->getFileFilter(); + QString fileName = QFileDialog::getOpenFileName(this, tr("Pick file to import from"), dir, filter); + if (fileName.isNull()) + return; + + ui->inputFileEdit->setText(fileName); + setFileDialogInitPathByFile(fileName); +} + +void ImportDialog::success() +{ + QWizard::accept(); +} + +void ImportDialog::hideCoverWidget() +{ + widgetCover->hide(); +} + +void ImportDialog::accept() +{ + if (!currentPlugin) + { + qCritical() << "Called ImportDialog::accept(), but no ImportPlugin is selected."; + return; + } + + ImportManager::StandardImportConfig stdConfig; + if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME)) + stdConfig.inputFileName = ui->inputFileEdit->text(); + + if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::CODEC)) + stdConfig.codec = ui->codecCombo->currentText(); + + Db* db = DBLIST->getByName(ui->dbNameCombo->currentText());; + if (!db) + { + qCritical() << "Called ImportDialog::accept(), but no database is selected."; + return; + } + + QString table = ui->tableNameCombo->currentText(); + + widgetCover->show(); + IMPORT_MANAGER->configure(currentPlugin->getDataSourceTypeName(), stdConfig); + IMPORT_MANAGER->importToTable(db, table); +} + +void ImportDialog::showEvent(QShowEvent* e) +{ + QWizard::showEvent(e); + ui->tableNameCombo->setFocus(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h new file mode 100644 index 0000000..c50703f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h @@ -0,0 +1,69 @@ +#ifndef IMPORTDIALOG_H +#define IMPORTDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class ImportDialog; +} + +class DbListModel; +class DbObjListModel; +class ImportPlugin; +class ConfigMapper; +class CfgEntry; +class WidgetCover; +class Db; + +class GUI_API_EXPORT ImportDialog : public QWizard +{ + Q_OBJECT + + public: + explicit ImportDialog(QWidget *parent = 0); + ~ImportDialog(); + + void setDbAndTable(Db* db, const QString& table); + void setDb(Db* db); + + protected: + void showEvent(QShowEvent* e); + + private: + void init(); + void initTablePage(); + void initDataSourcePage(); + void removeOldOptions(); + void updateStandardOptions(); + void updatePluginOptions(int& rows); + bool isPluginConfigValid() const; + + Ui::ImportDialog *ui = nullptr; + DbListModel* dbListModel = nullptr; + DbObjListModel* tablesModel = nullptr; + ConfigMapper* configMapper = nullptr; + QWidget* pluginOptionsWidget = nullptr; + ImportPlugin* currentPlugin = nullptr; + QHash pluginConfigOk; + WidgetCover* widgetCover = nullptr; + + private slots: + void handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg); + void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled); + void refreshTables(); + void pluginSelected(); + void updateValidation(); + void pageChanged(); + void browseForInputFile(); + void success(); + void hideCoverWidget(); + + public slots: + void accept(); + + signals: + void dsPageCompleteChanged(); +}; + +#endif // IMPORTDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui new file mode 100644 index 0000000..b853ab8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui @@ -0,0 +1,230 @@ + + + ImportDialog + + + + 0 + 0 + 511 + 406 + + + + Import data + + + QWizard::CancelButtonOnLeft|QWizard::NoDefaultButton + + + + Table to import to + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Table + + + + + + + Database + + + + + + + . + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + + Data source to import from + + + + + + #scrollArea { background: transparent; } + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 269 + 280 + + + + #scrollAreaWidgetContents { background: transparent; } + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Data source type + + + + + + + + + + + + Options + + + + + + Input file: + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + ... + + + + + + + + + + Text encoding: + + + + + + + + + + + + + Data source options + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + VerifiableWizardPage + QWizardPage +
    common/verifiablewizardpage.h
    + 1 +
    +
    + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp new file mode 100644 index 0000000..d835dd1 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp @@ -0,0 +1,468 @@ +#include "indexdialog.h" +#include "ui_indexdialog.h" +#include "schemaresolver.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "services/notifymanager.h" +#include "common/utils_sql.h" +#include "db/chainexecutor.h" +#include "dbtree/dbtree.h" +#include "ddlpreviewdialog.h" +#include "uiconfig.h" +#include "services/config.h" +#include "uiutils.h" +#include "sqlite3.h" +#include "windows/editorwindow.h" +#include "services/codeformatter.h" +#include +#include +#include +#include +#include +#include + +IndexDialog::IndexDialog(Db* db, QWidget *parent) : + QDialog(parent), + db(db), + ui(new Ui::IndexDialog) +{ + init(); +} + +IndexDialog::IndexDialog(Db* db, const QString& index, QWidget* parent) : + QDialog(parent), + db(db), + index(index), + ui(new Ui::IndexDialog) +{ + existingIndex = true; + init(); +} + +IndexDialog::~IndexDialog() +{ + delete ui; +} + +void IndexDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void IndexDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + if (!db || !db->isOpen()) + { + qCritical() << "Created IndexDialog for null or closed database."; + notifyError(tr("Tried to open index dialog for closed or inexisting database.")); + reject(); + return; + } + + ui->columnsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + ui->partialIndexEdit->setDb(db); + + connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); + + columnStateSignalMapping = new QSignalMapper(this); + connect(columnStateSignalMapping, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int))); + + SchemaResolver resolver(db); + ui->tableCombo->addItem(QString::null); + ui->tableCombo->addItems(resolver.getTables()); + connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateTable(QString))); + connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateValidation())); + if (existingIndex) + ui->tableCombo->setEnabled(false); + + if (db->getDialect() == Dialect::Sqlite3) + { + connect(ui->partialIndexCheck, SIGNAL(toggled(bool)), this, SLOT(updatePartialConditionState())); + connect(ui->partialIndexEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation())); + connect(ui->partialIndexEdit, SIGNAL(textChanged()), this, SLOT(updateValidation())); + ui->partialIndexEdit->setVirtualSqlExpression("SELECT %1"); + updatePartialConditionState(); + ui->columnsTable->setColumnHidden(1, false); + } + else + { + ui->partialIndexCheck->setVisible(false); + ui->partialIndexEdit->setVisible(false); + ui->columnsTable->setColumnHidden(1, true); + } + + readCollations(); + + ui->ddlEdit->setSqliteVersion(db->getVersion()); + + if (index.isNull()) + createIndex = SqliteCreateIndexPtr::create(); + else + readIndex(); + + originalCreateIndex = SqliteCreateIndexPtr::create(*createIndex); + + ui->nameEdit->setText(index); + setTable(createIndex->table); + + if (!index.isNull()) + applyIndex(); + + updateValidation(); + + ui->nameEdit->setFocus(); +} + +void IndexDialog::readIndex() +{ + SchemaResolver resolver(db); + SqliteQueryPtr parsedObject = resolver.getParsedObject(index, SchemaResolver::INDEX); + if (!parsedObject.dynamicCast()) + { + notifyError(tr("Could not process index %1 correctly. Unable to open an index dialog.").arg(index)); + reject(); + return; + } + + createIndex = parsedObject.dynamicCast(); +} + +void IndexDialog::buildColumns() +{ + // Clean up + ui->columnsTable->setRowCount(0); + columnCheckBoxes.clear(); + sortComboBoxes.clear(); + collateComboBoxes.clear(); + + totalColumns = tableColumns.size(); + ui->columnsTable->setRowCount(totalColumns); + + int row = 0; + foreach (const QString& column, tableColumns) + buildColumn(column, row++); +} + +void IndexDialog::updateTable(const QString& value) +{ + table = value; + + SchemaResolver resolver(db); + tableColumns = resolver.getTableColumns(table); + + buildColumns(); +} + +void IndexDialog::updateValidation() +{ + bool tableOk = ui->tableCombo->currentIndex() > 0; + bool colSelected = false; + + if (tableOk) + { + foreach (QCheckBox* cb, columnCheckBoxes) + { + if (cb->isChecked()) + { + colSelected = true; + break; + } + } + } + + bool partialConditionOk = (!ui->partialIndexCheck->isChecked() || + (ui->partialIndexEdit->isSyntaxChecked() && !ui->partialIndexEdit->haveErrors())); + + setValidState(ui->tableCombo, tableOk, tr("Pick the table for the index.")); + setValidState(ui->columnsTable, colSelected, tr("Select at least one column.")); + setValidState(ui->partialIndexCheck, partialConditionOk, tr("Enter a valid condition.")); + + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(colSelected && partialConditionOk); +} + +void IndexDialog::setTable(const QString& value) +{ + ui->tableCombo->setCurrentText(value); +} + +void IndexDialog::readCollations() +{ + SchemaResolver resolver(db); + QStringList collList = resolver.getCollations(); + + if (collList.size() > 0) + collList.prepend(""); + + collations.setStringList(collList); +} + +void IndexDialog::buildColumn(const QString& name, int row) +{ + int col = 0; + + QWidget* checkParent = new QWidget(); + QHBoxLayout* layout = new QHBoxLayout(); + QMargins margins = layout->contentsMargins(); + margins.setTop(0); + margins.setBottom(0); + margins.setLeft(4); + margins.setRight(4); + layout->setContentsMargins(margins); + checkParent->setLayout(layout); + + QCheckBox* check = new QCheckBox(name); + checkParent->layout()->addWidget(check); + + ui->columnsTable->setCellWidget(row, col++, checkParent); + columnStateSignalMapping->setMapping(check, row); + connect(check, SIGNAL(toggled(bool)), columnStateSignalMapping, SLOT(map())); + connect(check, SIGNAL(toggled(bool)), this, SLOT(updateValidation())); + columnCheckBoxes << check; + + QComboBox* collation = nullptr; + if (db->getDialect() == Dialect::Sqlite3) + { + collation = new QComboBox(); + collation->setEditable(true); + collation->lineEdit()->setPlaceholderText(tr("default", "index dialog")); + collation->setModel(&collations); + ui->columnsTable->setCellWidget(row, col++, collation); + collateComboBoxes << collation; + } + else + { + col++; + } + + QComboBox* sortOrder = new QComboBox(); + sortOrder->setToolTip(tr("Sort order", "table constraints")); + ui->columnsTable->setCellWidget(row, col++, sortOrder); + sortComboBoxes << sortOrder; + + QStringList sortList = {"", sqliteSortOrder(SqliteSortOrder::ASC), sqliteSortOrder(SqliteSortOrder::DESC)}; + sortOrder->addItems(sortList); + + totalColumns++; + + updateColumnState(row); +} + +void IndexDialog::updateColumnState(int row) +{ + bool enabled = columnCheckBoxes[row]->isChecked(); + sortComboBoxes[row]->setEnabled(enabled); + if (db->getDialect() == Dialect::Sqlite3) + collateComboBoxes[row]->setEnabled(enabled); +} + +void IndexDialog::updatePartialConditionState() +{ + ui->partialIndexEdit->setEnabled(ui->partialIndexCheck->isChecked()); + updateValidation(); +} + +void IndexDialog::updateDdl() +{ + rebuildCreateIndex(); + QString formatted = FORMATTER->format("sql", createIndex->detokenize(), db); + ui->ddlEdit->setPlainText(formatted); +} + +void IndexDialog::tabChanged(int tab) +{ + if (tab == 1) + updateDdl(); +} + +void IndexDialog::applyColumnValues() +{ + Dialect dialect = db->getDialect(); + int row; + foreach (SqliteIndexedColumn* idxCol, createIndex->indexedColumns) + { + row = indexOf(tableColumns, idxCol->name, Qt::CaseInsensitive); + if (row == -1) + { + qCritical() << "Cannot find column from index in the table columns! Indexed column:" << idxCol->name + << ", table columns:" << tableColumns << ", index name:" << index << ", table name:" << table; + continue; + } + + columnCheckBoxes[row]->setChecked(true); + updateColumnState(row); + sortComboBoxes[row]->setCurrentText(sqliteSortOrder(idxCol->sortOrder)); + if (dialect == Dialect::Sqlite3) + collateComboBoxes[row]->setCurrentText(idxCol->collate); + } +} + +void IndexDialog::applyIndex() +{ + applyColumnValues(); + + ui->partialIndexCheck->setChecked(createIndex->where != nullptr); + if (createIndex->where) + ui->partialIndexEdit->setPlainText(createIndex->where->detokenize()); +} + +SqliteIndexedColumn* IndexDialog::addIndexedColumn(const QString& name) +{ + SqliteIndexedColumn* idxCol = new SqliteIndexedColumn(); + idxCol->name = name; + idxCol->setParent(createIndex.data()); + createIndex->indexedColumns << idxCol; + return idxCol; +} + +void IndexDialog::rebuildCreateIndex() +{ + createIndex = SqliteCreateIndexPtr::create(); + createIndex->index = ui->nameEdit->text(); + if (ui->tableCombo->currentIndex() > -1) + createIndex->table = ui->tableCombo->currentText(); + + createIndex->uniqueKw = ui->uniqueCheck->isChecked(); + + SqliteIndexedColumn* idxCol = nullptr; + int i = -1; + for (const QString& column : tableColumns) + { + i++; + + if (!columnCheckBoxes[i]->isChecked()) + continue; + + idxCol = addIndexedColumn(column); + if (!collateComboBoxes[i]->currentText().isEmpty()) + idxCol->collate = collateComboBoxes[i]->currentText(); + + if (sortComboBoxes[i]->currentIndex() > 0) + idxCol->sortOrder = sqliteSortOrder(sortComboBoxes[i]->currentText()); + } + + if (ui->partialIndexCheck->isChecked()) + { + if (createIndex->where) + delete createIndex->where; + + Parser parser(db->getDialect()); + SqliteExpr* expr = parser.parseExpr(ui->partialIndexEdit->toPlainText()); + + if (expr) + { + expr->setParent(createIndex.data()); + createIndex->where = expr; + } + else + { + qCritical() << "Could not parse expression from partial index condition: " << ui->partialIndexEdit->toPlainText() + << ", the CREATE INDEX statement will be incomplete."; + } + } + + createIndex->rebuildTokens(); +} + +void IndexDialog::queryDuplicates() +{ + static QString queryTpl = QStringLiteral("SELECT %1 FROM %2 GROUP BY %3 HAVING %4;\n"); + static QString countTpl = QStringLiteral("count(%1) AS %2"); + static QString countColNameTpl = QStringLiteral("count(%1)"); + static QString countConditionTpl = QStringLiteral("count(%1) > 1"); + + Dialect dialect = db->getDialect(); + + QStringList cols; + QStringList grpCols; + QStringList countCols; + QString wrappedCol; + QString countColName; + int i = 0; + for (const QString& column : tableColumns) + { + if (!columnCheckBoxes[i++]->isChecked()) + continue; + + wrappedCol = wrapObjIfNeeded(column, dialect); + cols << wrappedCol; + grpCols << wrappedCol; + countColName = wrapObjIfNeeded(countColNameTpl.arg(column), dialect); + cols << countTpl.arg(wrappedCol, countColName); + countCols << countConditionTpl.arg(wrappedCol); + } + + EditorWindow* editor = MAINWINDOW->openSqlEditor(); + editor->setCurrentDb(db); + + QString sqlCols = cols.join(", "); + QString sqlGrpCols = grpCols.join(", "); + QString sqlCntCols = countCols.join(" AND "); + QString sqlTable = wrapObjIfNeeded(ui->tableCombo->currentText(), dialect); + editor->setContents(queryTpl.arg(sqlCols, sqlTable, sqlGrpCols, sqlCntCols)); + editor->execute(); +} + +void IndexDialog::accept() +{ + rebuildCreateIndex(); + + Dialect dialect = db->getDialect(); + + QStringList sqls; + if (existingIndex) + sqls << QString("DROP INDEX %1").arg(wrapObjIfNeeded(originalCreateIndex->index, dialect)); + + sqls << createIndex->detokenize(); + + if (!CFG_UI.General.DontShowDdlPreview.get()) + { + DdlPreviewDialog dialog(db, this); + dialog.setDdl(sqls); + if (dialog.exec() != QDialog::Accepted) + return; + } + + ChainExecutor executor; + executor.setDb(db); + executor.setAsync(false); + executor.setQueries(sqls); + executor.exec(); + + if (executor.getSuccessfulExecution()) + { + CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath()); + + QDialog::accept(); + DBTREE->refreshSchema(db); + return; + } + + if (executor.getErrors().size() == 1 && executor.getErrors().first().first == SQLITE_CONSTRAINT) + { + int res = QMessageBox::critical(this, + tr("Error", "index dialog"), + tr("Cannot create unique index, because values in selected columns are not unique. " + "Would you like to execute SELECT query to see problematic values?"), + QMessageBox::Yes, + QMessageBox::No); + if (res == QMessageBox::Yes) + { + QDialog::reject(); + queryDuplicates(); + } + } + else + { + QMessageBox::critical(this, tr("Error", "index dialog"), tr("An error occurred while executing SQL statements:\n%1") + .arg(executor.getErrorsMessages().join(",\n")), QMessageBox::Ok); + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h new file mode 100644 index 0000000..1f9d1f8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h @@ -0,0 +1,72 @@ +#ifndef INDEXDIALOG_H +#define INDEXDIALOG_H + +#include "db/db.h" +#include "guiSQLiteStudio_global.h" +#include "parser/ast/sqlitecreateindex.h" +#include +#include + +namespace Ui { + class IndexDialog; +} + +class QGridLayout; +class QSignalMapper; +class QCheckBox; +class QComboBox; + +class GUI_API_EXPORT IndexDialog : public QDialog +{ + Q_OBJECT + + public: + IndexDialog(Db* db, QWidget *parent = 0); + IndexDialog(Db* db, const QString& index, QWidget *parent = 0); + ~IndexDialog(); + + void setTable(const QString& value); + + protected: + void changeEvent(QEvent *e); + + private: + void init(); + void readIndex(); + void readCollations(); + void buildColumn(const QString& name, int row); + void applyColumnValues(); + void applyIndex(); + SqliteIndexedColumn* addIndexedColumn(const QString& name); + void rebuildCreateIndex(); + void queryDuplicates(); + + bool existingIndex = false; + Db* db = nullptr; + QString table; + QString index; + SqliteCreateIndexPtr createIndex; + SqliteCreateIndexPtr originalCreateIndex; + QStringList tableColumns; + QSignalMapper* columnStateSignalMapping = nullptr; + QStringListModel collations; + QList columnCheckBoxes; + QList sortComboBoxes; + QList collateComboBoxes; + int totalColumns = 0; + Ui::IndexDialog *ui = nullptr; + + private slots: + void updateValidation(); + void buildColumns(); + void updateTable(const QString& value); + void updateColumnState(int row); + void updatePartialConditionState(); + void updateDdl(); + void tabChanged(int tab); + + public slots: + void accept(); +}; + +#endif // INDEXDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui new file mode 100644 index 0000000..e231550 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui @@ -0,0 +1,195 @@ + + + IndexDialog + + + + 0 + 0 + 491 + 410 + + + + + 400 + 300 + + + + Index dialog + + + + + + 0 + + + + Index + + + + + + On table: + + + + + + + Index name: + + + + + + + Partial index condition + + + + + + + + 0 + 1 + + + + + + + + Unique index + + + + + + + + 0 + 2 + + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + + Column + + + + + Collation + + + + + Sort + + + + + + + + + + + + + + + DDL + + + + + + true + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    +
    + + tabWidget + partialIndexCheck + partialIndexEdit + buttonBox + ddlEdit + + + + + buttonBox + accepted() + IndexDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + IndexDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp new file mode 100644 index 0000000..fb78367 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp @@ -0,0 +1,97 @@ +#include "messagelistdialog.h" +#include "iconmanager.h" +#include "ui_messagelistdialog.h" +#include +#include + +MessageListDialog::MessageListDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::MessageListDialog) +{ + ui->setupUi(this); + ui->message->setVisible(false); +} + +MessageListDialog::MessageListDialog(const QString& message, QWidget* parent) : + QDialog(parent), + ui(new Ui::MessageListDialog) +{ + ui->setupUi(this); + ui->buttonBox->clear(); + ui->buttonBox->addButton(QDialogButtonBox::Yes); + ui->buttonBox->addButton(QDialogButtonBox::No); + ui->message->setText(message); +} + +MessageListDialog::~MessageListDialog() +{ + delete ui; +} + +void MessageListDialog::addMessage(const QString& text, const QBrush& background) +{ + addMessage(QIcon(), text, background); +} + +void MessageListDialog::addMessage(const QIcon& icon, const QString& text, const QBrush& background) +{ + QListWidgetItem* item = new QListWidgetItem(); + item->setText(text); + item->setBackground(background); + item->setIcon(icon); + ui->listWidget->addItem(item); +} + +void MessageListDialog::addInfo(const QString& text) +{ + addMessage(ICONS.STATUS_INFO, text, getGradient(0, 0, 1, 0.2)); +} + +void MessageListDialog::addWarning(const QString& text) +{ + addMessage(ICONS.STATUS_WARNING, text, getGradient(0.8, 0.8, 0, 0.4)); +} + +void MessageListDialog::addError(const QString& text) +{ + addMessage(ICONS.STATUS_ERROR, text, getGradient(0.6, 0, 0, 0.6)); +} + +void MessageListDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +QBrush MessageListDialog::getGradient(qreal r, qreal g, qreal b, qreal a) const +{ + QLinearGradient gradient(0, 0, 20, 120); + gradient.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0)); + gradient.setColorAt(1, QColor::fromRgbF(r, g, b, a)); + + return QBrush(gradient); +} + +void MessageListDialog::showEvent(QShowEvent*) +{ + adjustSize(); +} + +void MessageListDialog::resizeEvent(QResizeEvent*) +{ + QFontMetrics metrics = ui->listWidget->fontMetrics(); + QRect rect = ui->listWidget->rect(); + int cnt = ui->listWidget->count(); + QListWidgetItem* item = nullptr; + for (int row = 0; row < cnt; row++) + { + item = ui->listWidget->item(row); + item->setSizeHint(metrics.boundingRect(rect, Qt::TextWordWrap|Qt::TextLongestVariant, item->text()).size() + QSize(0, 10)); + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h new file mode 100644 index 0000000..e20d9f8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h @@ -0,0 +1,37 @@ +#ifndef MESSAGELISTDIALOG_H +#define MESSAGELISTDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class MessageListDialog; +} + +class GUI_API_EXPORT MessageListDialog : public QDialog +{ + Q_OBJECT + + public: + explicit MessageListDialog(QWidget *parent = 0); + explicit MessageListDialog(const QString& message, QWidget *parent = 0); + ~MessageListDialog(); + + void addMessage(const QString& text, const QBrush& background = QBrush()); + void addMessage(const QIcon& icon, const QString& text, const QBrush& background = QBrush()); + void addInfo(const QString& text); + void addWarning(const QString& text); + void addError(const QString& text); + + protected: + void changeEvent(QEvent *e); + void showEvent(QShowEvent*); + void resizeEvent(QResizeEvent*); + + private: + QBrush getGradient(qreal r, qreal g, qreal b, qreal a) const; + + Ui::MessageListDialog *ui = nullptr; +}; + +#endif // MESSAGELISTDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui new file mode 100644 index 0000000..10ee6b8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui @@ -0,0 +1,84 @@ + + + MessageListDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + + + + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + MessageListDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MessageListDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp new file mode 100644 index 0000000..36b400b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp @@ -0,0 +1,275 @@ +#include "newconstraintdialog.h" +#include "ui_newconstraintdialog.h" +#include "iconmanager.h" +#include + +NewConstraintDialog::NewConstraintDialog(SqliteCreateTable* createTable, Db* db, QWidget *parent) : + QDialog(parent), + ui(new Ui::NewConstraintDialog), + type(ConstraintDialog::TABLE), + db(db), + createTable(createTable) +{ + ui->setupUi(this); + init(); +} + +NewConstraintDialog::NewConstraintDialog(SqliteCreateTable::Column* column, Db* db, QWidget* parent) : + QDialog(parent), + ui(new Ui::NewConstraintDialog), + type(ConstraintDialog::COLUMN), + db(db), + columnStmt(column) +{ + ui->setupUi(this); + createTable = dynamic_cast(column->parent()); + init(); +} + +NewConstraintDialog::NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable* createTable, Db* db, QWidget* parent) : + NewConstraintDialog(createTable, db, parent) +{ + predefinedConstraintType = constraintType; +} + +NewConstraintDialog::NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable::Column* column, Db* db, QWidget* parent) : + NewConstraintDialog(column, db, parent) +{ + predefinedConstraintType = constraintType; +} + +NewConstraintDialog::~NewConstraintDialog() +{ + delete ui; + if (constraintDialog) + delete constraintDialog; +} + +SqliteStatement* NewConstraintDialog::getConstraint() +{ + return constrStatement; +} + +void NewConstraintDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void NewConstraintDialog::init() +{ + switch (type) + { + case ConstraintDialog::TABLE: + { + initTable(); + break; + } + case ConstraintDialog::COLUMN: + { + initColumn(); + break; + } + } + adjustSize(); + setMaximumSize(size()); + setMinimumSize(size()); +} + +void NewConstraintDialog::initTable() +{ + addButton(ICONS.CONSTRAINT_PRIMARY_KEY, tr("Primary Key", "new constraint dialog"), SLOT(createTablePk())); + if (createTable->dialect == Dialect::Sqlite3) + addButton(ICONS.CONSTRAINT_FOREIGN_KEY, tr("Foreign Key", "new constraint dialog"), SLOT(createTableFk())); + + addButton(ICONS.CONSTRAINT_UNIQUE, tr("Unique", "new constraint dialog"), SLOT(createTableUnique())); + addButton(ICONS.CONSTRAINT_CHECK, tr("Check", "new constraint dialog"), SLOT(createTableCheck())); +} + +void NewConstraintDialog::initColumn() +{ + addButton(ICONS.CONSTRAINT_PRIMARY_KEY, tr("Primary Key", "new constraint dialog"), SLOT(createColumnPk())); + if (createTable->dialect == Dialect::Sqlite3) + addButton(ICONS.CONSTRAINT_FOREIGN_KEY, tr("Foreign Key", "new constraint dialog"), SLOT(createColumnFk())); + + addButton(ICONS.CONSTRAINT_UNIQUE, tr("Unique", "new constraint dialog"), SLOT(createColumnUnique())); + addButton(ICONS.CONSTRAINT_CHECK, tr("Check", "new constraint dialog"), SLOT(createColumnCheck())); + addButton(ICONS.CONSTRAINT_NOT_NULL, tr("Not NULL", "new constraint dialog"), SLOT(createColumnNotNull())); + if (createTable->dialect == Dialect::Sqlite3) + addButton(ICONS.CONSTRAINT_COLLATION, tr("Collate", "new constraint dialog"), SLOT(createColumnCollate())); + + addButton(ICONS.CONSTRAINT_DEFAULT, tr("Default", "new constraint dialog"), SLOT(createColumnDefault())); +} + +void NewConstraintDialog::addButton(const Icon& icon, const QString text, const char* slot) +{ + QCommandLinkButton* btn = new QCommandLinkButton(); + btn->setIcon(icon); + btn->setText(text); + connect(btn, SIGNAL(clicked()), this, slot); + ui->container->layout()->addWidget(btn); +} + +int NewConstraintDialog::createColumnConstraint(ConstraintDialog::Constraint constraintType) +{ + SqliteCreateTable::Column::Constraint* constraint = new SqliteCreateTable::Column::Constraint(); + switch (constraintType) + { + case ConstraintDialog::PK: + constraint->type = SqliteCreateTable::Column::Constraint::PRIMARY_KEY; + break; + case ConstraintDialog::FK: + constraint->type = SqliteCreateTable::Column::Constraint::FOREIGN_KEY; + break; + case ConstraintDialog::UNIQUE: + constraint->type = SqliteCreateTable::Column::Constraint::UNIQUE; + break; + case ConstraintDialog::NOTNULL: + constraint->type = SqliteCreateTable::Column::Constraint::NOT_NULL; + break; + case ConstraintDialog::CHECK: + constraint->type = SqliteCreateTable::Column::Constraint::CHECK; + break; + case ConstraintDialog::COLLATE: + constraint->type = SqliteCreateTable::Column::Constraint::COLLATE; + break; + case ConstraintDialog::DEFAULT: + constraint->type = SqliteCreateTable::Column::Constraint::DEFAULT; + break; + case ConstraintDialog::UNKNOWN: + break; + } + + constrStatement = constraint; + constrStatement->setParent(columnStmt); + + return editConstraint(); +} + +int NewConstraintDialog::createTableConstraint(ConstraintDialog::Constraint constraintType) +{ + SqliteCreateTable::Constraint* constraint = new SqliteCreateTable::Constraint(); + switch (constraintType) + { + case ConstraintDialog::PK: + constraint->type = SqliteCreateTable::Constraint::PRIMARY_KEY; + break; + case ConstraintDialog::FK: + constraint->type = SqliteCreateTable::Constraint::FOREIGN_KEY; + break; + case ConstraintDialog::UNIQUE: + constraint->type = SqliteCreateTable::Constraint::UNIQUE; + break; + case ConstraintDialog::CHECK: + constraint->type = SqliteCreateTable::Constraint::CHECK; + break; + case ConstraintDialog::NOTNULL: + case ConstraintDialog::COLLATE: + case ConstraintDialog::DEFAULT: + case ConstraintDialog::UNKNOWN: + break; + } + + constrStatement = constraint; + constrStatement->setParent(createTable); + + return editConstraint(); +} + +int NewConstraintDialog::editConstraint() +{ + switch (type) + { + case ConstraintDialog::TABLE: + constraintDialog = new ConstraintDialog(ConstraintDialog::NEW, dynamic_cast(constrStatement), + createTable.data(), db, parentWidget()); + break; + case ConstraintDialog::COLUMN: + constraintDialog = new ConstraintDialog(ConstraintDialog::NEW, dynamic_cast(constrStatement), + columnStmt.data(), db, parentWidget()); + break; + } + + connect(constraintDialog, SIGNAL(rejected()), this, SLOT(reject())); + connect(constraintDialog, SIGNAL(accepted()), this, SLOT(accept())); + + hide(); + return constraintDialog->exec(); +} + +void NewConstraintDialog::createTablePk() +{ + createTableConstraint(ConstraintDialog::PK); +} + +void NewConstraintDialog::createTableFk() +{ + createTableConstraint(ConstraintDialog::FK); +} + +void NewConstraintDialog::createTableUnique() +{ + createTableConstraint(ConstraintDialog::UNIQUE); +} + +void NewConstraintDialog::createTableCheck() +{ + createTableConstraint(ConstraintDialog::CHECK); +} + +void NewConstraintDialog::createColumnPk() +{ + createColumnConstraint(ConstraintDialog::PK); +} + +void NewConstraintDialog::createColumnFk() +{ + createColumnConstraint(ConstraintDialog::FK); +} + +void NewConstraintDialog::createColumnUnique() +{ + createColumnConstraint(ConstraintDialog::UNIQUE); +} + +void NewConstraintDialog::createColumnCheck() +{ + createColumnConstraint(ConstraintDialog::CHECK); +} + +void NewConstraintDialog::createColumnNotNull() +{ + createColumnConstraint(ConstraintDialog::NOTNULL); +} + +void NewConstraintDialog::createColumnDefault() +{ + createColumnConstraint(ConstraintDialog::DEFAULT); +} + +void NewConstraintDialog::createColumnCollate() +{ + createColumnConstraint(ConstraintDialog::COLLATE); +} + +int NewConstraintDialog::exec() +{ + if (predefinedConstraintType == ConstraintDialog::UNKNOWN) + return QDialog::exec(); + + switch (type) + { + case ConstraintDialog::TABLE: + return createTableConstraint(predefinedConstraintType); + case ConstraintDialog::COLUMN: + return createColumnConstraint(predefinedConstraintType); + } + + return QDialog::Rejected; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h new file mode 100644 index 0000000..e374312 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h @@ -0,0 +1,65 @@ +#ifndef NEWCONSTRAINTDIALOG_H +#define NEWCONSTRAINTDIALOG_H + +#include "parser/ast/sqlitecreatetable.h" +#include "db/db.h" +#include "dialogs/constraintdialog.h" +#include "iconmanager.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +namespace Ui { + class NewConstraintDialog; +} + +class GUI_API_EXPORT NewConstraintDialog : public QDialog +{ + Q_OBJECT + + public: + explicit NewConstraintDialog(SqliteCreateTable* createTable, Db* db, QWidget *parent = 0); + explicit NewConstraintDialog(SqliteCreateTable::Column* column, Db* db, QWidget *parent = 0); + explicit NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable* createTable, Db* db, QWidget *parent = 0); + explicit NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable::Column* column, Db* db, QWidget *parent = 0); + ~NewConstraintDialog(); + + SqliteStatement* getConstraint(); + int exec(); + + protected: + void changeEvent(QEvent *e); + + private: + void init(); + void initTable(); + void initColumn(); + void addButton(const Icon& icon, const QString text, const char* slot); + int createColumnConstraint(ConstraintDialog::Constraint constraintType); + int createTableConstraint(ConstraintDialog::Constraint constraintType); + int editConstraint(); + + Ui::NewConstraintDialog *ui = nullptr; + ConstraintDialog::Type type; + Db* db = nullptr; + ConstraintDialog::Constraint predefinedConstraintType = ConstraintDialog::UNKNOWN; + SqliteStatement* constrStatement = nullptr; + QPointer createTable; + QPointer columnStmt; + ConstraintDialog* constraintDialog = nullptr; + + private slots: + void createTablePk(); + void createTableFk(); + void createTableUnique(); + void createTableCheck(); + void createColumnPk(); + void createColumnFk(); + void createColumnUnique(); + void createColumnCheck(); + void createColumnNotNull(); + void createColumnDefault(); + void createColumnCollate(); +}; + +#endif // NEWCONSTRAINTDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui new file mode 100644 index 0000000..20fc5ce --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui @@ -0,0 +1,75 @@ + + + NewConstraintDialog + + + + 0 + 0 + 400 + 300 + + + + + 300 + 0 + + + + New constraint + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + + + + + + + buttonBox + accepted() + NewConstraintDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NewConstraintDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp new file mode 100644 index 0000000..1c73de0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp @@ -0,0 +1,68 @@ +#include "newversiondialog.h" +#include "services/pluginmanager.h" +#include "sqlitestudio.h" +#include "ui_newversiondialog.h" +#include "services/config.h" +#include + +NewVersionDialog::NewVersionDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewVersionDialog) +{ + init(); +} + +NewVersionDialog::~NewVersionDialog() +{ + delete ui; +} + +void NewVersionDialog::setUpdates(const QList& updates) +{ + QTableWidgetItem* item = nullptr; + QString currVersion; + int row = 0; + ui->updateList->setRowCount(updates.size()); + for (const UpdateManager::UpdateEntry& entry : updates) + { + if (entry.compontent == "SQLiteStudio") + currVersion = SQLITESTUDIO->getVersionString(); + else + currVersion = PLUGINS->getPrintableVersion(entry.compontent); + + item = new QTableWidgetItem(entry.compontent); + ui->updateList->setItem(row, 0, item); + + item = new QTableWidgetItem(currVersion); + ui->updateList->setItem(row, 1, item); + + item = new QTableWidgetItem(entry.version); + ui->updateList->setItem(row, 2, item); + + row++; + } + ui->updateList->resizeColumnsToContents(); +} + +void NewVersionDialog::init() +{ + ui->setupUi(this); + + connect(ui->abortButton, SIGNAL(clicked()), this, SLOT(reject())); + connect(ui->updateButton, SIGNAL(clicked()), this, SLOT(installUpdates())); + connect(ui->checkOnStartupCheck, &QCheckBox::clicked, [=](bool checked) + { + CFG_CORE.General.CheckUpdatesOnStartup.set(checked); + }); +} + +void NewVersionDialog::installUpdates() +{ + UPDATES->update(); + close(); +} + +void NewVersionDialog::showEvent(QShowEvent*) +{ + ui->checkOnStartupCheck->setChecked(CFG_CORE.General.CheckUpdatesOnStartup.get()); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h new file mode 100644 index 0000000..784c6cf --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h @@ -0,0 +1,34 @@ +#ifndef NEWVERSIONDIALOG_H +#define NEWVERSIONDIALOG_H + +#include "services/updatemanager.h" +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class NewVersionDialog; +} + +class GUI_API_EXPORT NewVersionDialog : public QDialog +{ + Q_OBJECT + + public: + explicit NewVersionDialog(QWidget *parent = 0); + ~NewVersionDialog(); + + void setUpdates(const QList& updates); + + protected: + void showEvent(QShowEvent*); + + private: + void init(); + + Ui::NewVersionDialog *ui = nullptr; + + private slots: + void installUpdates(); +}; + +#endif // NEWVERSIONDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui new file mode 100644 index 0000000..6f50e7f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui @@ -0,0 +1,144 @@ + + + NewVersionDialog + + + + 0 + 0 + 400 + 307 + + + + SQLiteStudio updates + + + + + + + 75 + true + + + + New updates are available! + + + Qt::AlignCenter + + + + + + + Qt::NoFocus + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + false + + + true + + + false + + + 20 + + + 20 + + + + Component + + + + + Current version + + + + + Update version + + + + + + + + + + + Check for updates on startup + + + + + + + + + + Update to new version! + + + + :/icons/img/get_update.png:/icons/img/get_update.png + + + + 24 + 24 + + + + The update will be automatically downloaded and installed. This will also restart application at the end. + + + + + + + Not now. + + + + :/icons/img/abort24.png:/icons/img/abort24.png + + + + 24 + 24 + + + + Don't install the update and close this window. + + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp new file mode 100644 index 0000000..5dc506f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp @@ -0,0 +1,119 @@ +#include "populateconfigdialog.h" +#include "ui_populateconfigdialog.h" +#include "plugins/populateplugin.h" +#include "services/populatemanager.h" +#include "sqlitestudio.h" +#include "formmanager.h" +#include "configmapper.h" +#include "uiutils.h" +#include +#include +#include + +PopulateConfigDialog::PopulateConfigDialog(PopulateEngine* engine, const QString& column, const QString& pluginName, QWidget *parent) : + QDialog(parent), + ui(new Ui::PopulateConfigDialog), + engine(engine), + column(column), + pluginName(pluginName) +{ + init(); +} + +PopulateConfigDialog::~PopulateConfigDialog() +{ + safe_delete(configMapper); + delete ui; +} + +int PopulateConfigDialog::exec() +{ + QString formName = engine->getPopulateConfigFormName(); + if (formName.isNull()) + { + qCritical() << "Null form name from populating engine."; + return QDialog::Rejected; + } + + innerWidget = FORMS->createWidget(formName); + if (!innerWidget) + return QDialog::Rejected; + + configMapper->bindToConfig(innerWidget); + ui->contents->layout()->addWidget(innerWidget); + adjustSize(); + validateEngine(); + return QDialog::exec(); +} + +void PopulateConfigDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + + QString headerString = tr("Configuring %1 for column %2").arg(pluginName, column); + ui->headerLabel->setText(headerString ); + + configMapper = new ConfigMapper(engine->getConfig()); + connect(configMapper, SIGNAL(modified()), this, SLOT(validateEngine())); + + connect(POPULATE_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(validationResultFromPlugin(bool,CfgEntry*,QString))); + connect(POPULATE_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool))); + connect(POPULATE_MANAGER, SIGNAL(widgetPropertyFromPlugin(CfgEntry*,QString,QVariant)), this, SLOT(widgetPropertyFromPlugin(CfgEntry*,QString,QVariant))); +} + +void PopulateConfigDialog::validateEngine() +{ + engine->validateOptions(); +} + +void PopulateConfigDialog::validationResultFromPlugin(bool valid, CfgEntry* key, const QString& msg) +{ + QWidget* w = configMapper->getBindWidgetForConfig(key); + if (w) + setValidState(w, valid, msg); + + if (valid == pluginConfigOk.contains(key)) // if state changed + { + if (!valid) + pluginConfigOk[key] = false; + else + pluginConfigOk.remove(key); + } + updateState(); +} + +void PopulateConfigDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled) +{ + QWidget* w = configMapper->getBindWidgetForConfig(key); + if (!w) + return; + + w->setVisible(visible); + w->setEnabled(enabled); +} + + +void PopulateConfigDialog::widgetPropertyFromPlugin(CfgEntry* key, const QString& propName, const QVariant& value) +{ + QWidget* w = configMapper->getBindWidgetForConfig(key); + if (!w) + return; + + w->setProperty(propName.toLatin1().constData(), value); +} + +void PopulateConfigDialog::updateState() +{ + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(pluginConfigOk.size() == 0); +} + + +void PopulateConfigDialog::showEvent(QShowEvent* e) +{ + QVariant prop = innerWidget->property("initialSize"); + if (prop.isValid()) + resize(prop.toSize() + QSize(0, ui->headerLabel->height() + ui->line->height())); + + QDialog::showEvent(e); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h new file mode 100644 index 0000000..45bc333 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h @@ -0,0 +1,47 @@ +#ifndef POPULATECONFIGDIALOG_H +#define POPULATECONFIGDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +class PopulateEngine; +class ConfigMapper; +class CfgEntry; + +namespace Ui { + class PopulateConfigDialog; +} + +class GUI_API_EXPORT PopulateConfigDialog : public QDialog +{ + Q_OBJECT + + public: + explicit PopulateConfigDialog(PopulateEngine* engine, const QString& column, const QString& pluginName, QWidget *parent = 0); + ~PopulateConfigDialog(); + + int exec(); + + protected: + void showEvent(QShowEvent* e); + + private: + void init(); + + Ui::PopulateConfigDialog *ui = nullptr; + PopulateEngine* engine = nullptr; + ConfigMapper* configMapper = nullptr; + QHash pluginConfigOk; + QString column; + QString pluginName; + QWidget* innerWidget = nullptr; + + private slots: + void validateEngine(); + void validationResultFromPlugin(bool valid, CfgEntry* key, const QString& msg); + void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled); + void widgetPropertyFromPlugin(CfgEntry* key, const QString& propName, const QVariant& value); + void updateState(); +}; + +#endif // POPULATECONFIGDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui new file mode 100644 index 0000000..ac90f02 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui @@ -0,0 +1,99 @@ + + + PopulateConfigDialog + + + + 0 + 0 + 400 + 300 + + + + Populating configuration + + + + + + + + + Qt::RichText + + + + + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + PopulateConfigDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PopulateConfigDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp new file mode 100644 index 0000000..ca3fd31 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp @@ -0,0 +1,332 @@ +#include "populatedialog.h" +#include "ui_populatedialog.h" +#include "dblistmodel.h" +#include "dbobjlistmodel.h" +#include "services/dbmanager.h" +#include "schemaresolver.h" +#include "services/pluginmanager.h" +#include "plugins/populateplugin.h" +#include "populateconfigdialog.h" +#include "uiutils.h" +#include "services/populatemanager.h" +#include "common/widgetcover.h" +#include +#include +#include +#include +#include +#include + +PopulateDialog::PopulateDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::PopulateDialog) +{ + init(); +} + +PopulateDialog::~PopulateDialog() +{ + delete ui; +} + +void PopulateDialog::setDbAndTable(Db* db, const QString& table) +{ + ui->databaseCombo->setCurrentText(db->getName()); + ui->tableCombo->setCurrentText(table); +} + +void PopulateDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Populate", "populate dialog button")); + + plugins = PLUGINS->getLoadedPlugins(); + qSort(plugins.begin(), plugins.end(), [](PopulatePlugin* p1, PopulatePlugin* p2) -> bool + { + return p1->getTitle().compare(p2->getTitle()) < 0; + }); + + for (PopulatePlugin* plugin : plugins) + pluginTitles << plugin->getTitle(); + + widgetCover = new WidgetCover(this); + widgetCover->setVisible(false); + + ui->scrollArea->setAutoFillBackground(false); + ui->scrollArea->viewport()->setAutoFillBackground(false); + ui->columnsWidget->setAutoFillBackground(false); + + dbListModel = new DbListModel(this); + dbListModel->setCombo(ui->databaseCombo); + dbListModel->setSortMode(DbListModel::SortMode::Alphabetical); + ui->databaseCombo->setModel(dbListModel); + + tablesModel = new DbObjListModel(this); + tablesModel->setIncludeSystemObjects(false); + tablesModel->setType(DbObjListModel::ObjectType::TABLE); + ui->tableCombo->setModel(tablesModel); + refreshTables(); + + connect(ui->databaseCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshTables())); + connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshColumns())); + connect(POPULATE_MANAGER, SIGNAL(populatingFinished()), widgetCover, SLOT(hide())); + connect(POPULATE_MANAGER, SIGNAL(populatingSuccessful()), this, SLOT(finished())); +} + +PopulateEngine* PopulateDialog::getEngine(int selectedPluginIndex) +{ + if (selectedPluginIndex < 0 || selectedPluginIndex >= plugins.size()) + { + qCritical() << "Selected populate plugin out of range!"; + return nullptr; + } + + return plugins[selectedPluginIndex]->createEngine(); +} + +void PopulateDialog::deleteEngines(const QList& engines) +{ + for (PopulateEngine* engine : engines) + delete engine; +} + +void PopulateDialog::rebuildEngines() +{ + int row = 0; + for (const ColumnEntry& entry : columnEntries) + { + pluginSelected(entry.combo, entry.combo->currentIndex()); + updateColumnState(row++, false); + } +} + +void PopulateDialog::refreshTables() +{ + db = DBLIST->getByName(ui->databaseCombo->currentText()); + if (db) + tablesModel->setDb(db); + + updateState(); +} + +void PopulateDialog::refreshColumns() +{ + for (const ColumnEntry& entry : columnEntries) + { + delete entry.check; + delete entry.combo; + delete entry.button; + } + columnEntries.clear(); + safe_delete(buttonMapper); + safe_delete(checkMapper); + + delete ui->columnsLayout; + ui->columnsLayout = new QGridLayout(); + ui->columnsWidget->setLayout(ui->columnsLayout); + + if (!db) + { + qCritical() << "No Db while refreshing columns in PopulateDialog!"; + return; + } + + buttonMapper = new QSignalMapper(this); + connect(buttonMapper, SIGNAL(mapped(int)), this, SLOT(configurePlugin(int))); + + checkMapper = new QSignalMapper(this); + connect(checkMapper, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int))); + + SchemaResolver resolver(db); + QStringList columns = resolver.getTableColumns(ui->tableCombo->currentText()); + QCheckBox* check = nullptr; + QComboBox* combo = nullptr; + QToolButton* btn = nullptr; + int row = 0; + for (const QString& column : columns) + { + check = new QCheckBox(column); + connect(check, SIGNAL(toggled(bool)), checkMapper, SLOT(map())); + checkMapper->setMapping(check, row); + + combo = new QComboBox(); + combo->addItems(pluginTitles); + connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(pluginSelected(int))); + + btn = new QToolButton(); + btn->setText(tr("Configure")); + connect(btn, SIGNAL(clicked()), buttonMapper, SLOT(map())); + buttonMapper->setMapping(btn, row); + + ui->columnsLayout->addWidget(check, row, 0); + ui->columnsLayout->addWidget(combo, row, 1); + ui->columnsLayout->addWidget(btn, row, 2); + columnEntries << ColumnEntry(check, combo, btn); + row++; + } + + rebuildEngines(); + + QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + ui->columnsLayout->addItem(spacer, row, 0, 1, 3); + + updateState(); +} + +void PopulateDialog::pluginSelected(int index) +{ + QComboBox* cb = dynamic_cast(sender()); + pluginSelected(cb, index); +} + +void PopulateDialog::pluginSelected(QComboBox* combo, int index) +{ + if (!combo) + return; + + ColumnEntry* entry = nullptr; + + int columnIndex = 0; + for (ColumnEntry& e : columnEntries) + { + if (e.combo == combo) + { + entry = &e; + break; + } + columnIndex++; + } + + if (!entry) + return; + + safe_delete(entry->engine); + + if (index < 0 || index >= plugins.size()) + return; + + entry->engine = plugins[index]->createEngine(); + updateColumnState(columnIndex); +} + +void PopulateDialog::configurePlugin(int index) +{ + if (index < 0 || index >= columnEntries.size()) + { + qCritical() << "Plugin configure index out of range:" << index << "," << columnEntries.size(); + return; + } + + PopulateEngine* engine = columnEntries[index].engine; + if (!engine->getConfig()) + { + qWarning() << "Called config on populate plugin, but it has no CfgMain."; + return; + } + + engine->getConfig()->savepoint(); + + PopulateConfigDialog dialog(engine, columnEntries[index].check->text(), columnEntries[index].combo->currentText(), this); + if (dialog.exec() != QDialog::Accepted) + engine->getConfig()->restore(); + + engine->getConfig()->release(); + + updateColumnState(index); +} + +void PopulateDialog::updateColumnState(int index, bool updateGlobalState) +{ + if (index < 0 || index >= columnEntries.size()) + { + qCritical() << "Column update called but index out of range:" << index << "," << columnEntries.size(); + return; + } + + bool checked = columnEntries[index].check->isChecked(); + bool hasConfig = columnEntries[index].engine->getConfig() != nullptr; + columnEntries[index].combo->setEnabled(checked); + columnEntries[index].button->setEnabled(checked && hasConfig); + + bool valid = true; + if (checked && hasConfig) + { + valid = columnEntries[index].engine->validateOptions(); + setValidState(columnEntries[index].button, valid, tr("Populating configuration for this column is invalid or incomplete.")); + } + + if (valid == columnsValid.contains(index)) // if state changed + { + if (!valid) + columnsValid[index] = false; + else + columnsValid.remove(index); + } + + if (updateGlobalState) + updateState(); +} + +void PopulateDialog::updateState() +{ + bool columnsOk = columnsValid.size() == 0; + bool dbOk = !ui->databaseCombo->currentText().isNull(); + bool tableOk = !ui->tableCombo->currentText().isNull(); + + bool colCountOk = false; + for (const ColumnEntry& entry : columnEntries) + { + if (entry.check->isChecked()) + { + colCountOk = true; + break; + } + } + + setValidState(ui->databaseCombo, dbOk, tr("Select database with table to populate")); + setValidState(ui->tableCombo, tableOk, tr("Select table to populate")); + setValidState(ui->columnsGroup, (!tableOk || colCountOk), tr("You have to select at least one column.")); + + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(columnsOk && tableOk && colCountOk); +} + +void PopulateDialog::finished() +{ + QDialog::accept(); +} + +void PopulateDialog::accept() +{ + if (!db) + return; + + QHash engines; + for (ColumnEntry& entry : columnEntries) + { + if (!entry.check->isChecked()) + continue; + + if (!entry.engine) + return; + + engines[entry.check->text()] = entry.engine; +// entry.engine = nullptr; // to avoid deleting it in the entry's destructor - worker will delete it after it's done + } + + QString table = ui->tableCombo->currentText(); + qint64 rows = ui->rowsSpin->value(); + + widgetCover->show(); + POPULATE_MANAGER->populate(db, table, engines, rows); +} + +PopulateDialog::ColumnEntry::ColumnEntry(QCheckBox* check, QComboBox* combo, QToolButton* button) : + check(check), combo(combo), button(button) +{ +} + +PopulateDialog::ColumnEntry::~ColumnEntry() +{ + safe_delete(engine); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h new file mode 100644 index 0000000..0ecc318 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h @@ -0,0 +1,76 @@ +#ifndef POPULATEDIALOG_H +#define POPULATEDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class PopulateDialog; +} + +class PopulatePlugin; +class PopulateEngine; +class QGridLayout; +class DbListModel; +class DbObjListModel; +class Db; +class QComboBox; +class QCheckBox; +class QToolButton; +class QSignalMapper; +class WidgetCover; + +class GUI_API_EXPORT PopulateDialog : public QDialog +{ + Q_OBJECT + + public: + explicit PopulateDialog(QWidget *parent = 0); + ~PopulateDialog(); + void setDbAndTable(Db* db, const QString& table); + + private: + struct GUI_API_EXPORT ColumnEntry + { + ColumnEntry(QCheckBox* check, QComboBox* combo, QToolButton* button); + ~ColumnEntry(); + + QCheckBox* check = nullptr; + QComboBox* combo = nullptr; + QToolButton* button = nullptr; + PopulateEngine* engine = nullptr; + }; + + void init(); + PopulateEngine* getEngine(int selectedPluginIndex); + void deleteEngines(const QList& engines); + void rebuildEngines(); + + Ui::PopulateDialog *ui = nullptr; + QGridLayout* columnsGrid = nullptr; + DbListModel* dbListModel = nullptr; + DbObjListModel* tablesModel = nullptr; + Db* db = nullptr; + QStringList pluginTitles; + QList plugins; + QList columnEntries; + QSignalMapper* checkMapper = nullptr; + QSignalMapper* buttonMapper = nullptr; + QHash columnsValid; + WidgetCover* widgetCover = nullptr; + + private slots: + void refreshTables(); + void refreshColumns(); + void pluginSelected(int index); + void pluginSelected(QComboBox* combo, int index); + void configurePlugin(int index); + void updateColumnState(int index, bool updateGlobalState = true); + void updateState(); + void finished(); + + public: + void accept(); +}; + +#endif // POPULATEDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui new file mode 100644 index 0000000..811b185 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui @@ -0,0 +1,158 @@ + + + PopulateDialog + + + + 0 + 0 + 447 + 358 + + + + Populate table + + + + + + + 0 + 0 + + + + Database + + + + + + + + + + + + + 0 + 0 + + + + Table + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + Columns + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 409 + 144 + + + + + + + + + + + + + Number of rows to populate: + + + + + + 1 + + + 999999999 + + + 100 + + + + + + + + + + + + buttonBox + accepted() + PopulateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PopulateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp new file mode 100644 index 0000000..a55464d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp @@ -0,0 +1,42 @@ +#include "quitconfirmdialog.h" +#include "ui_quitconfirmdialog.h" + +QuitConfirmDialog::QuitConfirmDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::QuitConfirmDialog) +{ + init(); +} + +QuitConfirmDialog::~QuitConfirmDialog() +{ + delete ui; +} + +void QuitConfirmDialog::addMessage(const QString& msg) +{ + ui->itemList->addItem(msg); +} + +void QuitConfirmDialog::setMessages(const QStringList& messages) +{ + for (const QString& msg : messages) + addMessage(msg); +} + +int QuitConfirmDialog::getMessageCount() const +{ + return ui->itemList->count(); +} + +void QuitConfirmDialog::init() +{ + ui->setupUi(this); + + QStyle* style = QApplication::style(); + int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize); + QIcon icon = style->standardIcon(QStyle::SP_MessageBoxQuestion); + + if (!icon.isNull()) + ui->iconLabel->setPixmap(icon.pixmap(iconSize, iconSize)); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h new file mode 100644 index 0000000..0907c9b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h @@ -0,0 +1,28 @@ +#ifndef QUITCONFIRMDIALOG_H +#define QUITCONFIRMDIALOG_H + +#include + +namespace Ui { + class QuitConfirmDialog; +} + +class QuitConfirmDialog : public QDialog +{ + Q_OBJECT + + public: + explicit QuitConfirmDialog(QWidget *parent = 0); + ~QuitConfirmDialog(); + + void addMessage(const QString& msg); + void setMessages(const QStringList& messages); + int getMessageCount() const; + + private: + void init(); + + Ui::QuitConfirmDialog *ui = nullptr; +}; + +#endif // QUITCONFIRMDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui new file mode 100644 index 0000000..6f97934 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui @@ -0,0 +1,83 @@ + + + QuitConfirmDialog + + + + 0 + 0 + 489 + 186 + + + + Uncommited changes + + + + + + Are you sure you want to quit the application? + +Following items are pending: + + + + + + + Qt::Horizontal + + + QDialogButtonBox::No|QDialogButtonBox::Yes + + + + + + + + + + + + + + + + + + + buttonBox + accepted() + QuitConfirmDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QuitConfirmDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp new file mode 100644 index 0000000..87a6d88 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp @@ -0,0 +1,75 @@ +#include "searchtextdialog.h" +#include "ui_searchtextdialog.h" +#include "searchtextlocator.h" +#include "common/unused.h" + +SearchTextDialog::SearchTextDialog(SearchTextLocator* textLocator, QWidget *parent) : + QDialog(parent), + ui(new Ui::SearchTextDialog), textLocator(textLocator) +{ + ui->setupUi(this); + connect(textLocator, SIGNAL(replaceAvailable(bool)), this, SLOT(setReplaceAvailable(bool))); +} + +SearchTextDialog::~SearchTextDialog() +{ + delete ui; +} + +void SearchTextDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void SearchTextDialog::showEvent(QShowEvent* e) +{ + UNUSED(e); + ui->findEdit->setFocus(); + ui->findEdit->selectAll(); + configModifiedState = true; + setReplaceAvailable(false); +} + +void SearchTextDialog::applyConfigToLocator() +{ + if (!configModifiedState) + return; + + textLocator->setCaseSensitive(ui->caseSensitiveCheck->isChecked()); + textLocator->setSearchBackwards(ui->backwardsCheck->isChecked()); + textLocator->setRegularExpression(ui->regExpCheck->isChecked()); + textLocator->setLookupString(ui->findEdit->text()); + configModifiedState = false; +} + +void SearchTextDialog::setReplaceAvailable(bool available) +{ + ui->replaceButton->setEnabled(available); +} + +void SearchTextDialog::on_findButton_clicked() +{ + applyConfigToLocator(); + textLocator->find(); +} + +void SearchTextDialog::on_replaceButton_clicked() +{ + applyConfigToLocator(); + textLocator->setReplaceString(ui->replaceEdit->text()); + textLocator->replaceAndFind(); +} + +void SearchTextDialog::on_replaceAllButton_clicked() +{ + applyConfigToLocator(); + textLocator->setReplaceString(ui->replaceEdit->text()); + textLocator->replaceAll(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h new file mode 100644 index 0000000..54f6f72 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h @@ -0,0 +1,39 @@ +#ifndef SEARCHTEXTDIALOG_H +#define SEARCHTEXTDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class SearchTextDialog; +} + +class SearchTextLocator; + +class GUI_API_EXPORT SearchTextDialog : public QDialog +{ + Q_OBJECT + + public: + explicit SearchTextDialog(SearchTextLocator* textLocator, QWidget *parent = 0); + ~SearchTextDialog(); + + protected: + void changeEvent(QEvent *e); + void showEvent(QShowEvent* e); + + private: + void applyConfigToLocator(); + + Ui::SearchTextDialog *ui = nullptr; + SearchTextLocator* textLocator = nullptr; + bool configModifiedState = false; + + private slots: + void setReplaceAvailable(bool available); + void on_findButton_clicked(); + void on_replaceButton_clicked(); + void on_replaceAllButton_clicked(); +}; + +#endif // SEARCHTEXTDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui new file mode 100644 index 0000000..ce9e12e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui @@ -0,0 +1,153 @@ + + + SearchTextDialog + + + + 0 + 0 + 403 + 184 + + + + Dialog + + + + + + + + + Find: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Case sensitive + + + + + + + Search backwards + + + + + + + Regular expression matching + + + + + + + + + + Replace && +find next + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + Replace with: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Replace all + + + + + + + Find + + + true + + + + + + + findEdit + replaceEdit + caseSensitiveCheck + regExpCheck + backwardsCheck + findButton + replaceButton + replaceAllButton + buttonBox + + + + + buttonBox + accepted() + SearchTextDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SearchTextDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp new file mode 100644 index 0000000..b1451eb --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp @@ -0,0 +1,248 @@ +#include "sortdialog.h" +#include "ui_sortdialog.h" +#include "iconmanager.h" +#include "common/unused.h" +#include +#include +#include + +SortDialog::SortDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SortDialog) +{ + ui->setupUi(this); + + initActions(); + ui->list->header()->setSectionResizeMode(0, QHeaderView::Stretch); + + connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateButtons())); + connect(ui->list, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(itemChanged(QTreeWidgetItem*,int))); + connect(ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(reset())); + connect(ui->list->model(), &QAbstractItemModel::rowsInserted, [=](const QModelIndex & parent, int start, int end) + { + UNUSED(parent); + UNUSED(end); + rebuildComboForItem(ui->list->topLevelItem(start)); + }); +} + +SortDialog::~SortDialog() +{ + delete ui; +} + +void SortDialog::setColumns(const QStringList& columns) +{ + originalColumns = columns; + ui->list->clear(); + + QTreeWidgetItem* item = nullptr; + for (int row = 0, total = columns.size(); row < total; ++row) + { + item = new QTreeWidgetItem({columns[row], "ASC"}); + item->setData(2, Qt::UserRole, row); + fixItemFlags(item); + ui->list->insertTopLevelItem(row, item); + item->setCheckState(0, Qt::Unchecked); + } + ui->list->setHeaderLabels({tr("Column"), tr("Order")}); + updateButtons(); +} + +QueryExecutor::SortList SortDialog::getSortOrder() const +{ + QueryExecutor::SortList sortOrder; + + QTreeWidgetItem* item = nullptr; + QComboBox* combo = nullptr; + for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row) + { + item = ui->list->topLevelItem(row); + if (item->checkState(0) != Qt::Checked) + continue; + + combo = dynamic_cast(ui->list->itemWidget(item, 1)); + sortOrder << QueryExecutor::Sort((combo->currentText() == "ASC" ? Qt::AscendingOrder : Qt::DescendingOrder), item->data(2, Qt::UserRole).toInt()); + } + return sortOrder; +} + +void SortDialog::setSortOrder(const QueryExecutor::SortList& sortOrder) +{ + // Translate sort order into more usable (in here) form + QHash checkedColumns; + QList checkedColumnsOrder; + for (const QueryExecutor::Sort& sort : sortOrder) + { + checkedColumns[sort.column] = sort.order; + checkedColumnsOrder << sort.column; + } + + // Select proper columns and set order + bool checked; + QTreeWidgetItem* item = nullptr; + QComboBox* combo = nullptr; + for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row) + { + item = ui->list->topLevelItem(row); + checked = checkedColumns.contains(item->data(2, Qt::UserRole).toInt()); + item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); + + combo = dynamic_cast(ui->list->itemWidget(item, 1)); + combo->setCurrentText(checkedColumns[row] == QueryExecutor::Sort::DESC ? "DESC" : "ASC"); + } + + // Get selected items as an ordered list of items (in order as defined in the sort order), so we can easly relocate them + QList orderedItems; + for (int row : checkedColumnsOrder) + orderedItems << ui->list->topLevelItem(row); + + // Move selected items in front, in the same order as they were mentioned in the sort order + int newRow = 0; + for (QTreeWidgetItem* itemToMove : orderedItems) + { + ui->list->takeTopLevelItem(ui->list->indexOfTopLevelItem(itemToMove)); + ui->list->insertTopLevelItem(newRow++, itemToMove); + } + + updateState(); +} + +QToolBar* SortDialog::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return nullptr; +} + +void SortDialog::updateState(QTreeWidgetItem* item) +{ + QComboBox* combo = dynamic_cast(ui->list->itemWidget(item, 1)); + if (!combo) + return; + + combo->setEnabled(item->checkState(0) == Qt::Checked); +} + +void SortDialog::updateState() +{ + for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row) + updateState(ui->list->topLevelItem(row)); +} + +void SortDialog::fixItemFlags(QTreeWidgetItem* item) +{ + Qt::ItemFlags flags = item->flags(); + flags |= Qt::ItemNeverHasChildren; + flags |= Qt::ItemIsUserCheckable; + flags ^= Qt::ItemIsDropEnabled; + flags ^= Qt::ItemIsEditable; + item->setFlags(flags); +} + +void SortDialog::rebuildComboForItem(QTreeWidgetItem* item) +{ + QComboBox* combo = new QComboBox(); + combo->addItems({"ASC", "DESC"}); + combo->setCurrentText(item->text(1)); + combo->setEnabled(item->checkState(0) == Qt::Checked); + ui->list->setItemWidget(item, 1, combo); + item->setSizeHint(1, combo->sizeHint()); // bug in Qt? without this comboboxes were misaligned vertically + + connect(combo, &QComboBox::currentTextChanged, [item](const QString& newText) + { + item->setText(1, newText); + }); + + updateSortLabel(); +} + +void SortDialog::updateSortLabel() +{ + QStringList entries; + QTreeWidgetItem* item = nullptr; + for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row) + { + item = ui->list->topLevelItem(row); + if (item->checkState(0) != Qt::Checked) + continue; + + entries << item->text(0) + " " + item->text(1); + } + + if (entries.size() == 0) + { + ui->sortByLabel->setVisible(false); + } + else + { + static QString label = tr("Sort by: %1"); + ui->sortByLabel->setText(label.arg(entries.join(", "))); + ui->sortByLabel->setVisible(true); + } +} + +void SortDialog::itemChanged(QTreeWidgetItem* item, int column) +{ + if (column == 0) + updateState(item); + + updateSortLabel(); +} + +void SortDialog::reset() +{ + setColumns(originalColumns); +} + +void SortDialog::updateButtons() +{ + QTreeWidgetItem* item = ui->list->currentItem(); + actionMap[MOVE_UP]->setEnabled(item && ui->list->itemAbove(item) != nullptr); + actionMap[MOVE_DOWN]->setEnabled(item && ui->list->itemBelow(item) != nullptr); +} + +void SortDialog::moveCurrentUp() +{ + QTreeWidgetItem* item = ui->list->currentItem(); + if (!item) + return; + + int row = ui->list->indexOfTopLevelItem(item); + if (row < 1) + return; + + ui->list->takeTopLevelItem(row); + ui->list->insertTopLevelItem(row - 1, item); + + QModelIndex idx = ui->list->model()->index(row - 1, 0); + ui->list->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Rows|QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); + updateButtons(); +} + +void SortDialog::moveCurrentDown() +{ + QTreeWidgetItem* item = ui->list->currentItem(); + if (!item) + return; + + int row = ui->list->indexOfTopLevelItem(item); + if (row + 1 >= ui->list->topLevelItemCount()) + return; + + ui->list->takeTopLevelItem(row); + ui->list->insertTopLevelItem(row + 1, item); + + QModelIndex idx = ui->list->model()->index(row + 1, 0); + ui->list->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Rows|QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); + updateButtons(); +} + +void SortDialog::createActions() +{ + createAction(MOVE_UP, ICONS.MOVE_UP, tr("Move column up"), this, SLOT(moveCurrentUp()), ui->toolbar, this); + createAction(MOVE_DOWN, ICONS.MOVE_DOWN, tr("Move column down"), this, SLOT(moveCurrentDown()), ui->toolbar, this); +} + +void SortDialog::setupDefShortcuts() +{ +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h new file mode 100644 index 0000000..a103d90 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h @@ -0,0 +1,60 @@ +#ifndef SORTDIALOG_H +#define SORTDIALOG_H + +#include "db/queryexecutor.h" +#include "common/extactioncontainer.h" +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class SortDialog; +} + +class QTreeWidgetItem; + +class GUI_API_EXPORT SortDialog : public QDialog, public ExtActionContainer +{ + Q_OBJECT + + public: + enum Action + { + MOVE_UP, + MOVE_DOWN + }; + + enum ToolBar + { + }; + + explicit SortDialog(QWidget *parent = 0); + ~SortDialog(); + + void setColumns(const QStringList& columns); + QueryExecutor::SortList getSortOrder() const; + void setSortOrder(const QueryExecutor::SortList& sortOrder); + QToolBar* getToolBar(int toolbar) const; + + protected: + void createActions(); + void setupDefShortcuts(); + + private: + void updateState(QTreeWidgetItem* item); + void updateState(); + void fixItemFlags(QTreeWidgetItem* item); + void rebuildComboForItem(QTreeWidgetItem* item); + void updateSortLabel(); + + Ui::SortDialog *ui = nullptr; + QStringList originalColumns; + + private slots: + void itemChanged(QTreeWidgetItem* item, int column); + void reset(); + void updateButtons(); + void moveCurrentUp(); + void moveCurrentDown(); +}; + +#endif // SORTDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui new file mode 100644 index 0000000..23bee81 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui @@ -0,0 +1,112 @@ + + + SortDialog + + + + 0 + 0 + 457 + 357 + + + + Sort by columns + + + + + + + + + + 2 + 0 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + false + + + + Column + + + + + Order + + + + + + + + + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + + + + + + + buttonBox + accepted() + SortDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SortDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp new file mode 100644 index 0000000..462e57f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp @@ -0,0 +1,52 @@ +#include "triggercolumnsdialog.h" +#include "ui_triggercolumnsdialog.h" + +#include + +TriggerColumnsDialog::TriggerColumnsDialog(QWidget *parent) : + QDialog(parent, Qt::Popup), + ui(new Ui::TriggerColumnsDialog) +{ + ui->setupUi(this); +} + +TriggerColumnsDialog::~TriggerColumnsDialog() +{ + delete ui; +} + +void TriggerColumnsDialog::addColumn(const QString& name, bool checked) +{ + QCheckBox* cb = new QCheckBox(name); + cb->setChecked(checked); + ui->mainWidget->layout()->addWidget(cb); + checkBoxList << cb; +} + +QStringList TriggerColumnsDialog::getCheckedColumns() const +{ + QStringList columns; + foreach (QCheckBox* cb, checkBoxList) + { + if (cb->isChecked()) + columns << cb->text(); + } + return columns; +} + +void TriggerColumnsDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void TriggerColumnsDialog::showEvent(QShowEvent*) +{ + adjustSize(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h new file mode 100644 index 0000000..1ba0d69 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h @@ -0,0 +1,33 @@ +#ifndef TRIGGERCOLUMNSDIALOG_H +#define TRIGGERCOLUMNSDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class TriggerColumnsDialog; +} + +class QCheckBox; + +class GUI_API_EXPORT TriggerColumnsDialog : public QDialog +{ + Q_OBJECT + + public: + explicit TriggerColumnsDialog(QWidget *parent = 0); + ~TriggerColumnsDialog(); + + void addColumn(const QString& name, bool checked); + QStringList getCheckedColumns() const; + + protected: + void changeEvent(QEvent *e); + void showEvent(QShowEvent*); + + private: + QList checkBoxList; + Ui::TriggerColumnsDialog *ui = nullptr; +}; + +#endif // TRIGGERCOLUMNSDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui new file mode 100644 index 0000000..4326fca --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui @@ -0,0 +1,117 @@ + + + TriggerColumnsDialog + + + + 0 + 0 + 334 + 300 + + + + Dialog + + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 75 + true + + + + Triggering columns: + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 320 + 239 + + + + + 0 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + buttonBox + accepted() + TriggerColumnsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TriggerColumnsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp new file mode 100644 index 0000000..0707bd3 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp @@ -0,0 +1,413 @@ +#include "triggerdialog.h" +#include "ui_triggerdialog.h" +#include "parser/ast/sqliteselect.h" +#include "services/notifymanager.h" +#include "parser/ast/sqliteexpr.h" +#include "triggercolumnsdialog.h" +#include "common/utils_sql.h" +#include "schemaresolver.h" +#include "parser/parser.h" +#include "iconmanager.h" +#include "db/chainexecutor.h" +#include "dbtree/dbtree.h" +#include "ddlpreviewdialog.h" +#include "uiconfig.h" +#include "services/config.h" +#include "uiutils.h" +#include "services/codeformatter.h" +#include +#include +#include + +TriggerDialog::TriggerDialog(Db* db, QWidget *parent) : + QDialog(parent), + db(db), + ui(new Ui::TriggerDialog) +{ + init(); +} + +TriggerDialog::~TriggerDialog() +{ + delete ui; +} + +void TriggerDialog::setParentTable(const QString& name) +{ + forTable = true; + table = name; + initTrigger(); +} + +void TriggerDialog::setParentView(const QString& name) +{ + forTable = false; + view = name; + initTrigger(); +} + +void TriggerDialog::setTrigger(const QString& name) +{ + trigger = name; + originalTriggerName = name; + existingTrigger = true; + initTrigger(); +} + +void TriggerDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void TriggerDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + + connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(updateDdlTab(int))); + connect(ui->actionColumns, SIGNAL(clicked()), this, SLOT(showColumnsDialog())); + + // On object combo + ui->onCombo->setEnabled(false); + connect(ui->onCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(tableChanged(QString))); + + // Action combo + ui->actionCombo->addItems({ + SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::DELETE), + SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::INSERT), + SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::UPDATE), + SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::UPDATE_OF) + }); + connect(ui->actionCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState())); + + // Scope combo + ui->scopeCombo->addItems({ + SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::null), + SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::FOR_EACH_ROW) + }); + if (db->getDialect() == Dialect::Sqlite2) + { + ui->scopeCombo->addItems({ + SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT) + }); + } + + // Precondition + connect(ui->preconditionCheck, SIGNAL(clicked()), this, SLOT(updateState())); + connect(ui->preconditionEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation())); + connect(ui->preconditionEdit, SIGNAL(textChanged()), this, SLOT(updateValidation())); + ui->preconditionEdit->setDb(db); + + // Code + connect(ui->codeEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation())); + connect(ui->codeEdit, SIGNAL(textChanged()), this, SLOT(updateValidation())); + ui->codeEdit->setDb(db); +} + +void TriggerDialog::initTrigger() +{ + // Name edit + ui->nameEdit->setText(trigger); + + if (trigger.isNull()) + { + createTrigger = SqliteCreateTriggerPtr::create(); + createTrigger->event = new SqliteCreateTrigger::Event(); + } + else + { + parseDdl(); + readTrigger(); + } + + // Event combo + if (forTable) + { + ui->whenCombo->addItems({ + SqliteCreateTrigger::time(SqliteCreateTrigger::Time::null), + SqliteCreateTrigger::time(SqliteCreateTrigger::Time::BEFORE), + SqliteCreateTrigger::time(SqliteCreateTrigger::Time::AFTER) + }); + } + else + { + ui->whenCombo->addItems({ + SqliteCreateTrigger::time(SqliteCreateTrigger::Time::INSTEAD_OF) + }); + ui->whenCombo->setEnabled(false); + ui->onLabel->setText(tr("On view:")); + } + + if (!view.isNull() || !table.isNull()) + { + readColumns(); + QString target = getTargetObjectName(); + ui->onCombo->addItem(target); + ui->onCombo->setCurrentText(target); + } + + // Precondition and code edits + setupVirtualSqls(); + + updateState(); +} + +void TriggerDialog::parseDdl() +{ + SchemaResolver resolver(db); + SqliteQueryPtr parsedObject = resolver.getParsedObject(trigger, SchemaResolver::TRIGGER); + if (!parsedObject.dynamicCast()) + { + notifyError(tr("Could not process trigger %1 correctly. Unable to open a trigger dialog.").arg(trigger)); + reject(); + return; + } + + createTrigger = parsedObject.dynamicCast(); + ddl = createTrigger->detokenize(); +} + +void TriggerDialog::readTrigger() +{ + if (!createTrigger) + return; + + forTable = createTrigger->eventTime != SqliteCreateTrigger::Time::INSTEAD_OF; + if (forTable) + table = createTrigger->table; + else + view = createTrigger->table; + + ui->onCombo->addItem(createTrigger->table); + ui->onCombo->setCurrentText(createTrigger->table); + ui->whenCombo->setCurrentText(SqliteCreateTrigger::time(createTrigger->eventTime)); + ui->actionCombo->setCurrentText(SqliteCreateTrigger::Event::typeToString(createTrigger->event->type)); + ui->scopeCombo->setCurrentText(SqliteCreateTrigger::scopeToString(createTrigger->scope)); + if (createTrigger->precondition) + { + ui->preconditionCheck->setChecked(true); + ui->preconditionEdit->setPlainText(createTrigger->precondition->detokenize()); + } + + if (createTrigger->queries.size() > 0) + { + QStringList sqls; + foreach (SqliteQuery* query, createTrigger->queries) + sqls << query->detokenize(); + + ui->codeEdit->setPlainText(sqls.join(";\n")+";"); + } +} + +void TriggerDialog::setupVirtualSqls() +{ + Dialect dialect = db->getDialect(); + static QString preconditionVirtSql = QStringLiteral("CREATE TRIGGER %1 BEFORE INSERT ON %2 WHEN %3 BEGIN SELECT 1; END;"); + static QString codeVirtSql = QStringLiteral("CREATE TRIGGER %1 BEFORE INSERT ON %2 BEGIN %3 END;"); + ui->codeEdit->setVirtualSqlCompleteSemicolon(true); + if (!trigger.isNull()) + { + if (createTrigger) // if false, then there was a parsing error in parseDdl(). + { + ui->preconditionEdit->setVirtualSqlExpression( + preconditionVirtSql.arg(wrapObjIfNeeded(trigger, dialect), + wrapObjIfNeeded(createTrigger->table, dialect), + "%1")); + + ui->codeEdit->setVirtualSqlExpression( + codeVirtSql.arg( + wrapObjIfNeeded(trigger, dialect), + wrapObjIfNeeded(createTrigger->table, dialect), + "%1")); + } + } + else if (!table.isNull() || !view.isNull()) + { + ui->preconditionEdit->setVirtualSqlExpression( + preconditionVirtSql.arg("trig", + wrapObjIfNeeded(getTargetObjectName(), dialect), + "%1")); + + ui->codeEdit->setVirtualSqlExpression( + codeVirtSql.arg("trig", + wrapObjIfNeeded(getTargetObjectName(), dialect), + "%1")); + } + else + { + qCritical() << "TriggerDialog is in invalid state. Called initTrigger() but none of trigger/table/view values are set."; + } +} + +void TriggerDialog::readColumns() +{ + SchemaResolver resolver(db); + if (!table.isNull()) + targetColumns = resolver.getTableColumns(table); + else if (!view.isNull()) + targetColumns = resolver.getViewColumns(view); + else + targetColumns.clear(); + + if (createTrigger) + selectedColumns = createTrigger->event->columnNames; +} + +QString TriggerDialog::getTargetObjectName() const +{ + if (!table.isNull()) + return table; + + return view; +} + +void TriggerDialog::rebuildTrigger() +{ + /* + * Trigger is not rebuilt into SqliteCreateTrigger, because it's impossible to parse + * precondition or queries if they are invalid and we still need an invalid queries + * to be presented on the DDL tab. + */ + static const QString tempDdl = QStringLiteral("CREATE TRIGGER %1%2 %3%4 ON %5%6%7 BEGIN %8 END;"); + + Dialect dialect = db->getDialect(); + QString trigName = wrapObjIfNeeded(ui->nameEdit->text(), dialect); + QString when = ui->whenCombo->currentText(); + QString action = ui->actionCombo->currentText(); + QString columns = ""; + QString target = wrapObjIfNeeded(getTargetObjectName(), dialect); + QString scope = ui->scopeCombo->currentText(); + QString precondition = ""; + QString queries = ui->codeEdit->toPlainText(); + + // Columns + SqliteCreateTrigger::Event::Type actionType = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText()); + if (actionType == SqliteCreateTrigger::Event::UPDATE_OF) + { + QStringList colNames; + foreach (const QString& colName, selectedColumns) + colNames << wrapObjIfNeeded(colName, dialect); + + columns = " "+colNames.join(", "); + } + + // Precondition + if (ui->preconditionCheck->isChecked()) + precondition = " WHEN "+ui->preconditionEdit->toPlainText(); + + // Queries + if (!queries.trimmed().endsWith(";")) + queries += ";"; + + // When + if (!when.isNull()) + when.prepend(" "); + + // Scope + if (!scope.isNull()) + scope.prepend(" "); + + ddl = tempDdl.arg(trigName).arg(when).arg(action).arg(columns).arg(target).arg(scope).arg(precondition).arg(queries); +} + +void TriggerDialog::updateState() +{ + SqliteCreateTrigger::Event::Type type = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText()); + ui->actionColumns->setEnabled(type == SqliteCreateTrigger::Event::UPDATE_OF); + ui->preconditionEdit->setEnabled(ui->preconditionCheck->isChecked()); + updateValidation(); +} + +void TriggerDialog::updateValidation() +{ + SqliteCreateTrigger::Event::Type type = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText()); + bool columnsOk = (type != SqliteCreateTrigger::Event::UPDATE_OF || selectedColumns.size() > 0); + + bool preconditionOk = (!ui->preconditionCheck->isChecked() || + (ui->preconditionEdit->isSyntaxChecked() && !ui->preconditionEdit->haveErrors())); + + bool codeOk = (ui->codeEdit->isSyntaxChecked() && !ui->codeEdit->haveErrors()); + + setValidState(ui->preconditionCheck, preconditionOk, tr("Enter a valid condition.")); + setValidState(ui->codeEdit, codeOk, tr("Enter a valid trigger code.")); + ui->actionColumns->setIcon(columnsOk ? ICONS.TRIGGER_COLUMNS : ICONS.TRIGGER_COLUMNS_INVALID); + + QPushButton* okButton = ui->buttonBox->button(QDialogButtonBox::Ok); + okButton->setEnabled(columnsOk && preconditionOk && codeOk); +} + +void TriggerDialog::showColumnsDialog() +{ + TriggerColumnsDialog dialog(this); + foreach (const QString& colName, targetColumns) + dialog.addColumn(colName, selectedColumns.contains(colName, Qt::CaseInsensitive)); + + if (dialog.exec() != QDialog::Accepted) + return; + + QStringList newColumns = dialog.getCheckedColumns(); + selectedColumns = newColumns; + updateValidation(); +} + +void TriggerDialog::updateDdlTab(int tabIdx) +{ + if (tabIdx != 1) + return; + + rebuildTrigger(); + QString formatted = FORMATTER->format("sql", ddl, db); + ui->ddlEdit->setPlainText(formatted); +} + +void TriggerDialog::tableChanged(const QString& newValue) +{ + ui->preconditionEdit->setTriggerContext(newValue); + ui->codeEdit->setTriggerContext(newValue); +} + +void TriggerDialog::accept() +{ + rebuildTrigger(); + + Dialect dialect = db->getDialect(); + + QStringList sqls; + if (existingTrigger) + sqls << QString("DROP TRIGGER %1").arg(wrapObjIfNeeded(originalTriggerName, dialect)); + + sqls << ddl; + + if (!CFG_UI.General.DontShowDdlPreview.get()) + { + DdlPreviewDialog dialog(db, this); + dialog.setDdl(sqls); + if (dialog.exec() != QDialog::Accepted) + return; + } + + ChainExecutor executor; + executor.setDb(db); + executor.setAsync(false); + executor.setQueries(sqls); + executor.exec(); + + if (executor.getSuccessfulExecution()) + { + CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath()); + + QDialog::accept(); + DBTREE->refreshSchema(db); + return; + } + + QMessageBox::critical(this, tr("Error", "trigger dialog"), tr("An error occurred while executing SQL statements:\n%1") + .arg(executor.getErrorsMessages().join(",\n")), QMessageBox::Ok); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h new file mode 100644 index 0000000..d8e7ed4 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h @@ -0,0 +1,62 @@ +#ifndef TRIGGERDIALOG_H +#define TRIGGERDIALOG_H + +#include "db/db.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class TriggerDialog; +} + +class GUI_API_EXPORT TriggerDialog : public QDialog +{ + Q_OBJECT + + public: + explicit TriggerDialog(Db* db, QWidget *parent = 0); + ~TriggerDialog(); + + void setParentTable(const QString& name); + void setParentView(const QString& name); + void setTrigger(const QString& name); + + protected: + void changeEvent(QEvent *e); + + private: + void init(); + void initTrigger(); + void parseDdl(); + void readTrigger(); + void setupVirtualSqls(); + void readColumns(); + QString getTargetObjectName() const; + void rebuildTrigger(); + + QString originalTriggerName; + QString trigger; + QString table; + QString view; + Db* db = nullptr; + bool forTable = true; + bool existingTrigger = false; + QStringList targetColumns; + QStringList selectedColumns; + QString ddl; + SqliteCreateTriggerPtr createTrigger; + Ui::TriggerDialog *ui = nullptr; + + private slots: + void updateState(); + void updateValidation(); + void showColumnsDialog(); + void updateDdlTab(int tabIdx); + void tableChanged(const QString& newValue); + + public slots: + void accept(); +}; + +#endif // TRIGGERDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui new file mode 100644 index 0000000..bf3da0a --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui @@ -0,0 +1,215 @@ + + + TriggerDialog + + + + 0 + 0 + 490 + 480 + + + + Trigger dialog + + + + + + 0 + + + + Trigger + + + + + + On table: + + + + + + + + + + Action: + + + + + + + + + + + + + + + + <p>SQL condition that will be evaluated before the actual trigger code. In case the condition returns false, the trigger will not be fired for that row.</p> + + + Pre-condition: + + + + + + + The scope is still not fully supported by the SQLite database. + + + + + + + Trigger name: + + + + + + + When: + + + + + + + List of columns for UPDATE OF action. + + + ... + + + + + + + Scope: + + + + + + + Code: + + + + + + + Trigger statements to be executed. + + + + + + + + 16777215 + 80 + + + + <p>SQL condition that will be evaluated before the actual trigger code. In case the condition returns false, the trigger will not be fired for that row.</p> + + + + + + + + DDL + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    +
    + + tabWidget + nameEdit + whenCombo + actionCombo + actionColumns + onCombo + scopeCombo + preconditionCheck + preconditionEdit + codeEdit + buttonBox + ddlEdit + + + + + buttonBox + accepted() + TriggerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TriggerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp new file mode 100644 index 0000000..5521245 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp @@ -0,0 +1,31 @@ +#include "versionconvertsummarydialog.h" +#include "ui_versionconvertsummarydialog.h" + +VersionConvertSummaryDialog::VersionConvertSummaryDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::VersionConvertSummaryDialog) +{ + ui->setupUi(this); + + ui->diffTable->setLeftLabel(tr("Before")); + ui->diffTable->setRightLabel(tr("After")); + ui->diffTable->horizontalHeader()->setVisible(true); +} + +VersionConvertSummaryDialog::~VersionConvertSummaryDialog() +{ + delete ui; +} + +void VersionConvertSummaryDialog::setSides(const QList >& data) +{ + ui->diffTable->setSides(data); +} + + +void VersionConvertSummaryDialog::showEvent(QShowEvent* e) +{ + QDialog::showEvent(e); + ui->diffTable->updateSizes(); + +} diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h new file mode 100644 index 0000000..fc63076 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h @@ -0,0 +1,28 @@ +#ifndef VERSIONCONVERTSUMMARYDIALOG_H +#define VERSIONCONVERTSUMMARYDIALOG_H + +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class VersionConvertSummaryDialog; +} + +class GUI_API_EXPORT VersionConvertSummaryDialog : public QDialog +{ + Q_OBJECT + + public: + explicit VersionConvertSummaryDialog(QWidget *parent = 0); + ~VersionConvertSummaryDialog(); + + void setSides(const QList>& data); + + protected: + void showEvent(QShowEvent* e); + + private: + Ui::VersionConvertSummaryDialog *ui = nullptr; +}; + +#endif // VERSIONCONVERTSUMMARYDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui new file mode 100644 index 0000000..a67db1e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui @@ -0,0 +1,91 @@ + + + VersionConvertSummaryDialog + + + + 0 + 0 + 600 + 497 + + + + Database version convert + + + + + + Following changes to the SQL statements will be made: + + + + + + + QAbstractItemView::NoSelection + + + false + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SqlCompareView + QTableWidget +
    sqlcompareview.h
    +
    +
    + + + + buttonBox + accepted() + VersionConvertSummaryDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + VersionConvertSummaryDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/formmanager.cpp b/SQLiteStudio3/guiSQLiteStudio/formmanager.cpp new file mode 100644 index 0000000..9df36d0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/formmanager.cpp @@ -0,0 +1,185 @@ +#include "formmanager.h" +#include "services/config.h" +#include "services/pluginmanager.h" +#include "sqlitestudio.h" +#include "uiloader.h" +#include "common/unused.h" +#include "common/global.h" +#include +#include +#include +#include +#include + +FormManager::FormManager() +{ + init(); +} + +FormManager::~FormManager() +{ + if (uiLoader) + { + delete uiLoader; + uiLoader = nullptr; + } +} + +QWidget* FormManager::createWidget(const QString& name) +{ + if (!widgetNameToFullPath.contains(name)) + { + qCritical() << "Asked for widget name which isn't managed by FormManager:" << name << ", while available widgets are:" + << widgetNameToFullPath.keys(); + return nullptr; + } + return createWidgetByFullPath(widgetNameToFullPath[name]); +} + +bool FormManager::hasWidget(const QString& name) +{ + return widgetNameToFullPath.contains(name); +} + +QStringList FormManager::getAvailableForms() const +{ + return widgetNameToFullPath.keys(); +} + +QWidget* FormManager::createWidgetByFullPath(const QString& path) +{ + QWidget* widget = uiLoader->load(path); + if (!widget) + { + qCritical() << "Error occured while loading ui file:" << path << ". Error message: " + << uiLoader->errorString(); + return nullptr; + } + return widget; +} + +void FormManager::rescanResources(Plugin* plugin, PluginType* pluginType) +{ + UNUSED(pluginType); + rescanResources(plugin->getName()); +} + +void FormManager::rescanResources(const QString& pluginName) +{ + if (PLUGINS->isBuiltIn(pluginName)) + return; + + for (const QString& widgetName : resourceForms) + widgetNameToFullPath.remove(widgetName); + + resourceForms.clear(); + loadRecurently(":/forms", ""); +} + +void FormManager::pluginsAboutToMassUnload() +{ + disconnect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(rescanResources(Plugin*,PluginType*))); + disconnect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(rescanResources(QString))); +} + +void FormManager::pluginsInitiallyLoaded() +{ + load(); + + connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(rescanResources(Plugin*,PluginType*))); + connect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(rescanResources(QString))); + connect(PLUGINS, SIGNAL(aboutToQuit()), this, SLOT(pluginsAboutToMassUnload())); + disconnect(PLUGINS, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(pluginsInitiallyLoaded())); +} + +void FormManager::init() +{ + uiLoader = new UiLoader(); + + if (PLUGINS->arePluginsInitiallyLoaded()) + pluginsInitiallyLoaded(); + else + connect(PLUGINS, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(pluginsInitiallyLoaded())); +} + +void FormManager::load() +{ + QStringList dirs; + dirs += qApp->applicationDirPath() + "/forms"; + dirs += ":/forms"; + dirs += QDir(CFG->getConfigDir()).absoluteFilePath("forms"); + + QString envDirs = SQLITESTUDIO->getEnv("SQLITESTUDIO_FORMS"); + if (!envDirs.isNull()) + dirs += envDirs.split(PATH_LIST_SEPARATOR); + + dirs += PLUGINS->getPluginDirs(); + +#ifdef FORMS_DIR + dirs += STRINGIFY(FORMS_DIR); +#endif + + foreach (QString dirPath, dirs) + loadRecurently(dirPath, ""); +} + +void FormManager::loadRecurently(const QString& path, const QString& prefix) +{ + static const QStringList fileExtensions = {"*.ui", "*.UI"}; + + QDir dir(path); + QString fullPath; + QString widgetName; + foreach (QFileInfo entry, dir.entryInfoList(fileExtensions, QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot|QDir::Readable)) + { + fullPath = entry.absoluteFilePath(); + if (entry.isDir()) + { + loadRecurently(fullPath, prefix+entry.fileName()+"_"); + continue; + } + + qDebug() << "Loading form file:" << fullPath; + + widgetName = getWidgetName(fullPath); + if (widgetName.isNull()) + continue; + + if (widgetNameToFullPath.contains(widgetName)) + { + qCritical() << "Widget named" << widgetName << "was already loaded by FormManager from file" << widgetNameToFullPath[widgetName] + << "therefore file" << fullPath << "will be ignored"; + continue; + } + + widgetNameToFullPath[widgetName] = fullPath; + if (fullPath.startsWith(":/")) + resourceForms << widgetName; + } +} + +QString FormManager::getWidgetName(const QString& path) +{ + static const QRegularExpression re(R"()"); + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + { + qWarning() << "Could not open" << path << "for reading. Form file ignored."; + return QString::null; + } + + QString contents = file.readAll(); + file.close(); + + QRegularExpressionMatch match = re.match(contents); + if (!match.hasMatch()) + { + qWarning() << "Could not match widget in" << path << " document. File ignored."; + return QString::null; + } + + QString widgetName = match.captured(1); + + return widgetName; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/formmanager.h b/SQLiteStudio3/guiSQLiteStudio/formmanager.h new file mode 100644 index 0000000..41f98ce --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/formmanager.h @@ -0,0 +1,43 @@ +#ifndef FORMMANAGER_H +#define FORMMANAGER_H + +#include "guiSQLiteStudio_global.h" +#include "mainwindow.h" +#include +#include + +class UiLoader; + +class GUI_API_EXPORT FormManager : public QObject +{ + Q_OBJECT + + public: + FormManager(); + virtual ~FormManager(); + + QWidget* createWidget(const QString& name); + bool hasWidget(const QString& name); + QStringList getAvailableForms() const; + + private: + void init(); + void load(); + void loadRecurently(const QString& path, const QString& prefix = ""); + QString getWidgetName(const QString& path); + QWidget* createWidgetByFullPath(const QString& path); + + UiLoader* uiLoader = nullptr; + QHash widgetNameToFullPath; + QStringList resourceForms; + + private slots: + void rescanResources(Plugin* plugin, PluginType* pluginType); + void rescanResources(const QString& pluginName); + void pluginsAboutToMassUnload(); + void pluginsInitiallyLoaded(); +}; + +#define FORMS MainWindow::getInstance()->getFormManager() + +#endif // FORMMANAGER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui b/SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui new file mode 100644 index 0000000..67f1bd6 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/forms/sqlformatterplugin.ui @@ -0,0 +1,46 @@ + + + Form + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Active SQL formatter plugin + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/formview.cpp b/SQLiteStudio3/guiSQLiteStudio/formview.cpp new file mode 100644 index 0000000..ab51f3e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/formview.cpp @@ -0,0 +1,274 @@ +#include "formview.h" +#include "common/unused.h" +#include "datagrid/sqlquerymodel.h" +#include "datagrid/sqlqueryview.h" +#include "widgetresizer.h" +#include "datagrid/sqlqueryitem.h" +#include "uiconfig.h" +#include "common/datawidgetmapper.h" +#include +#include +#include +#include + +CFG_KEYS_DEFINE(FormView) + +FormView::FormView(QWidget *parent) : + QScrollArea(parent) +{ + init(); +} + +void FormView::init() +{ + setWidgetResizable(true); + initActions(); + + dataMapper = new DataWidgetMapper(this); + dataMapper->setSubmitFilter([](QWidget* w) -> bool + { + return dynamic_cast(w)->isModified(); + }); + connect(dataMapper, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int))); + + contents = new QWidget(); + QVBoxLayout *contentsLayout = new QVBoxLayout(); + contentsLayout->setSpacing(spacing); + contentsLayout->setMargin(margins); + contents->setLayout(contentsLayout); + + connect(CFG_UI.General.DataEditorsOrder, SIGNAL(changed(QVariant)), this, SLOT(reload())); + + setWidget(contents); +} + +SqlQueryModel* FormView::getModel() const +{ + return model.data(); +} + +void FormView::setModel(SqlQueryModel* value) +{ + if (!model.isNull()) + { + disconnect(model.data(), SIGNAL(loadingEnded(bool)), this, SLOT(dataLoaded(bool))); + disconnect(value, SIGNAL(commitStatusChanged(bool)), this, SLOT(gridCommitRollbackStatusChanged())); + } + + model = value; + connect(value, SIGNAL(loadingEnded(bool)), this, SLOT(dataLoaded(bool))); + connect(value, SIGNAL(commitStatusChanged(bool)), this, SLOT(gridCommitRollbackStatusChanged())); +} + +void FormView::load() +{ + reloadInternal(); + dataMapper->toFirst(); +} + +void FormView::reload() +{ + int idx = dataMapper->getCurrentIndex(); + reloadInternal(); + dataMapper->setCurrentIndex(idx); +} + +void FormView::focusFirstEditor() +{ + if (editors.size() == 0) + return; + + editors.first()->focusThisEditor(); +} + +void FormView::reloadInternal() +{ + // Cleanup + dataMapper->clearMapping(); + foreach (QWidget* widget, widgets) + { + contents->layout()->removeWidget(widget); + delete widget; + } + widgets.clear(); + editors.clear(); + readOnly.clear(); + + // Recreate + dataMapper->setModel(model.data()); + int i = 0; + foreach (SqlQueryModelColumnPtr column, model->getColumns()) + addColumn(i++, column->displayName, column->dataType, (column->editionForbiddenReason.size() > 0)); +} + +bool FormView::isModified() const +{ + return valueModified; +} + +void FormView::addColumn(int colIdx, const QString& name, const DataType& dataType, bool readOnly) +{ + // Group with label + QString groupLabel = name; + if (!dataType.toString().isEmpty()) + groupLabel += " (" + dataType.toString() + ")"; + + QGroupBox* group = new QGroupBox(groupLabel); + QFont font = group->font(); + font.setBold(true); + group->setFont(font); + + QVBoxLayout *vbox = new QVBoxLayout(); + vbox->setSpacing(spacing); + vbox->setMargin(margins); + group->setLayout(vbox); + + // MultiEditor + MultiEditor* multiEditor = new MultiEditor(); + font.setBold(false); + multiEditor->setFont(font); + multiEditor->setReadOnly(readOnly); + dataMapper->addMapping(multiEditor, colIdx, "value"); + vbox->addWidget(multiEditor); + widgets << group; + editors << multiEditor; + contents->layout()->addWidget(group); + this->readOnly << readOnly; + + connect(multiEditor, SIGNAL(modified()), this, SLOT(editorValueModified())); + + // MultiEditor editors + multiEditor->setDataType(dataType); + + // Resizer + WidgetResizer* resizer = new WidgetResizer(Qt::Vertical); + resizer->setWidget(group); + resizer->setWidgetMinimumSize(0, minimumFieldHeight); + widgets << resizer; + contents->layout()->addWidget(resizer); +} + +bool FormView::isCurrentRowModifiedInGrid() +{ + if (!model) + return false; + + QModelIndex startIdx = model->index(gridView->getCurrentIndex().row(), 0); + QModelIndex endIdx = model->index(gridView->getCurrentIndex().row(), model->columnCount() - 1); + return model->findIndexes(startIdx, endIdx, SqlQueryItem::DataRole::UNCOMMITED, true, 1).size() > 0; +} + +void FormView::updateDeletedState() +{ + SqlQueryItem* item = model->itemFromIndex(dataMapper->getCurrentIndex(), 0); + if (!item) + return; + + bool deleted = item->isDeletedRow(); + int i = 0; + foreach (MultiEditor* editor, editors) + { + editor->setDeletedRow(deleted); + editor->setReadOnly(readOnly[i++] || deleted); + } +} + +void FormView::dataLoaded(bool successful) +{ + if (successful) + load(); +} + +void FormView::currentIndexChanged(int index) +{ + valueModified = false; + emit commitStatusChanged(); + + if (gridView.isNull()) + return; + + if (currentIndexUpdating) + return; + + currentIndexUpdating = true; + gridView->setCurrentRow(index); + currentIndexUpdating = false; + + // If row was deleted, we need to make fields readonly + updateDeletedState(); + + emit currentRowChanged(); +} + +void FormView::editorValueModified() +{ + valueModified = true; + emit commitStatusChanged(); +} + +void FormView::gridCommitRollbackStatusChanged() +{ + valueModified = isCurrentRowModifiedInGrid(); + emit commitStatusChanged(); +} + +void FormView::copyDataToGrid() +{ + dataMapper->submit(); +} + +void FormView::updateFromGrid() +{ + currentIndexUpdating = true; + + dataMapper->setCurrentIndex(gridView->getCurrentIndex().row()); + + // Already modified in grid? + valueModified = isCurrentRowModifiedInGrid(); + + currentIndexUpdating = false; + + updateDeletedState(); + + emit currentRowChanged(); +} + +SqlQueryView* FormView::getGridView() const +{ + return gridView.data(); +} + +void FormView::setGridView(SqlQueryView* value) +{ + gridView = value; +} + +int FormView::getCurrentRow() +{ + return dataMapper->getCurrentIndex(); +} + +void FormView::createActions() +{ + createAction(COMMIT, ICONS.COMMIT, tr("Commit row", "form view"), this, SIGNAL(requestForCommit()), this); + createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback row", "form view"), this, SIGNAL(requestForRollback()), this); + createAction(FIRST_ROW, ICONS.PAGE_FIRST, tr("First row", "form view"), this, SIGNAL(requestForFirstRow()), this); + createAction(PREV_ROW, ICONS.PAGE_PREV, tr("Previous row", "form view"), this, SIGNAL(requestForPrevRow()), this); + createAction(NEXT_ROW, ICONS.PAGE_NEXT, tr("Next row", "form view"), this, SIGNAL(requestForNextRow()), this); + createAction(LAST_ROW, ICONS.PAGE_LAST, tr("Last row", "form view"), this, SIGNAL(requestForLastRow()), this); + createAction(INSERT_ROW, ICONS.INSERT_ROW, tr("Insert new row", "form view"), this, SIGNAL(requestForRowInsert()), this); + createAction(DELETE_ROW, ICONS.DELETE_ROW, tr("Delete current row", "form view"), this, SIGNAL(requestForRowDelete()), this); +} + +void FormView::setupDefShortcuts() +{ + setShortcutContext({ROLLBACK, COMMIT, NEXT_ROW, PREV_ROW, FIRST_ROW, LAST_ROW, INSERT_ROW, DELETE_ROW}, Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(FormView, Action); +} + +QToolBar* FormView::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return nullptr; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/formview.h b/SQLiteStudio3/guiSQLiteStudio/formview.h new file mode 100644 index 0000000..a6a9708 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/formview.h @@ -0,0 +1,114 @@ +#ifndef FORMVIEW_H +#define FORMVIEW_H + +#include "guiSQLiteStudio_global.h" +#include "datagrid/sqlquerymodelcolumn.h" +#include "multieditor/multieditor.h" +#include +#include +#include +#include + +class SqlQueryModel; +class SqlQueryView; +class DataWidgetMapper; + +CFG_KEY_LIST(FormView, QObject::tr("Data form view"), + CFG_KEY_ENTRY(COMMIT, Qt::CTRL + Qt::Key_Return, QObject::tr("Commit changes for current row")) + CFG_KEY_ENTRY(ROLLBACK, Qt::CTRL + Qt::Key_Backspace, QObject::tr("Rollback changes for current row")) + CFG_KEY_ENTRY(FIRST_ROW, Qt::CTRL + Qt::Key_PageUp, QObject::tr("Go to first row on current page")) + CFG_KEY_ENTRY(NEXT_ROW, Qt::CTRL + Qt::Key_Right, QObject::tr("Go to next row")) + CFG_KEY_ENTRY(PREV_ROW, Qt::CTRL + Qt::Key_Left, QObject::tr("Go to previous row")) + CFG_KEY_ENTRY(LAST_ROW, Qt::CTRL + Qt::Key_PageDown, QObject::tr("Go to last row on current page")) + CFG_KEY_ENTRY(INSERT_ROW, Qt::Key_Insert, QObject::tr("Insert new row")) + CFG_KEY_ENTRY(DELETE_ROW, Qt::CTRL + Qt::Key_Delete, QObject::tr("Delete current row")) +) + +class GUI_API_EXPORT FormView : public QScrollArea, public ExtActionContainer +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + COMMIT, + ROLLBACK, + FIRST_ROW, + NEXT_ROW, + PREV_ROW, + LAST_ROW, + INSERT_ROW, + DELETE_ROW + }; + + enum ToolBar + { + }; + + explicit FormView(QWidget *parent = 0); + + void init(); + + SqlQueryModel* getModel() const; + void setModel(SqlQueryModel* value); + + bool isModified() const; + + SqlQueryView* getGridView() const; + void setGridView(SqlQueryView* value); + + int getCurrentRow(); + + protected: + void createActions(); + void setupDefShortcuts(); + QToolBar* getToolBar(int toolbar) const; + + private: + void reloadInternal(); + void addColumn(int colIdx, const QString& name, const DataType& dataType, bool readOnly); + bool isCurrentRowModifiedInGrid(); + void updateDeletedState(); + + static const int margins = 2; + static const int spacing = 2; + static const int minimumFieldHeight = 40; + + DataWidgetMapper* dataMapper = nullptr; + QPointer gridView; + QPointer model; + QWidget* contents = nullptr; + QList widgets; + QList editors; + QList readOnly; + bool valueModified = false; + bool currentIndexUpdating = false; + + private slots: + void dataLoaded(bool successful); + void currentIndexChanged(int index); + void editorValueModified(); + void gridCommitRollbackStatusChanged(); + + public slots: + void copyDataToGrid(); + void updateFromGrid(); + void load(); + void reload(); + void focusFirstEditor(); + + signals: + void commitStatusChanged(); + void currentRowChanged(); + void requestForCommit(); + void requestForRollback(); + void requestForNextRow(); + void requestForPrevRow(); + void requestForFirstRow(); + void requestForLastRow(); + void requestForRowInsert(); + void requestForRowDelete(); +}; + +#endif // FORMVIEW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro new file mode 100644 index 0000000..ac3cb5b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro @@ -0,0 +1,364 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-02-28T23:22:10 +# +#------------------------------------------------- + +QT += core gui uitools widgets xml svg + +include($$PWD/../dirs.pri) +include($$PWD/../utils.pri) + +OBJECTS_DIR = $$OBJECTS_DIR/guiSQLiteStudio +MOC_DIR = $$MOC_DIR/guiSQLiteStudio +UI_DIR = $$UI_DIR/guiSQLiteStudio + +linux: { + portable: { + DESTDIR = $$DESTDIR/lib + } +} + +TARGET = guiSQLiteStudio +TEMPLATE = lib + +CONFIG += c++11 +QMAKE_CXXFLAGS += -pedantic + +DEFINES += GUISQLITESTUDIO_LIBRARY + +SOURCES +=\ + mainwindow.cpp \ + iconmanager.cpp \ + dbtree/dbtreemodel.cpp \ + dbtree/dbtreeitem.cpp \ + dbtree/dbtree.cpp \ + dbtree/dbtreeview.cpp \ + actionentry.cpp \ + uiutils.cpp \ + dbtree/dbtreeitemdelegate.cpp \ + dbtree/dbtreeitemfactory.cpp \ + sqleditor.cpp \ + datagrid/sqlquerymodel.cpp \ + dblistmodel.cpp \ + mdiarea.cpp \ + statusfield.cpp \ + common/tablewidget.cpp \ + datagrid/sqlqueryitem.cpp \ + datagrid/sqlqueryview.cpp \ + datagrid/sqlquerymodelcolumn.cpp \ + datagrid/sqlqueryitemdelegate.cpp \ + common/extlineedit.cpp \ + common/intvalidator.cpp \ + common/widgetcover.cpp \ + mdiwindow.cpp \ + mdichild.cpp \ + taskbar.cpp \ + multieditor/multieditor.cpp \ + multieditor/multieditorwidget.cpp \ + widgetresizer.cpp \ + multieditor/multieditortext.cpp \ + multieditor/multieditornumeric.cpp \ + common/numericspinbox.cpp \ + multieditor/multieditordatetime.cpp \ + multieditor/multieditordate.cpp \ + multieditor/multieditortime.cpp \ + formview.cpp \ + multieditor/multieditorbool.cpp \ + multieditor/multieditorhex.cpp \ + qhexedit2/xbytearray.cpp \ + qhexedit2/qhexedit_p.cpp \ + qhexedit2/qhexedit.cpp \ + qhexedit2/commands.cpp \ + multieditor/multieditordialog.cpp \ + completer/completerwindow.cpp \ + completer/completermodel.cpp \ + completer/completeritemdelegate.cpp \ + completer/completerview.cpp \ + dialogs/searchtextdialog.cpp \ + searchtextlocator.cpp \ + windows/tablewindow.cpp \ + windows/editorwindow.cpp \ + datagrid/sqltablemodel.cpp \ + dataview.cpp \ + windows/tablestructuremodel.cpp \ + windows/tableconstraintsmodel.cpp \ + dialogs/columndialog.cpp \ + dialogs/columndialogconstraintsmodel.cpp \ + common/extactioncontainer.cpp \ + common/extaction.cpp \ + constraints/tableprimarykeypanel.cpp \ + constraints/constraintpanel.cpp \ + constraints/tableforeignkeypanel.cpp \ + constraints/tableuniquepanel.cpp \ + constraints/tablepkanduniquepanel.cpp \ + constraints/tablecheckpanel.cpp \ + constraints/columncheckpanel.cpp \ + constraints/constraintcheckpanel.cpp \ + constraints/columnforeignkeypanel.cpp \ + constraints/columnprimarykeypanel.cpp \ + constraints/columnuniquepanel.cpp \ + constraints/columnuniqueandnotnullpanel.cpp \ + constraints/columnnotnullpanel.cpp \ + constraints/columncollatepanel.cpp \ + constraints/columndefaultpanel.cpp \ + dialogs/constraintdialog.cpp \ + dialogs/newconstraintdialog.cpp \ + windows/constrainttabmodel.cpp \ + dialogs/messagelistdialog.cpp \ + windows/viewwindow.cpp \ + dialogs/configdialog.cpp \ + uiconfig.cpp \ + dialogs/indexdialog.cpp \ + sqlview.cpp \ + dialogs/triggerdialog.cpp \ + dialogs/triggercolumnsdialog.cpp \ + dbobjectdialogs.cpp \ + common/fontedit.cpp \ + configwidgets/styleconfigwidget.cpp \ + common/colorbutton.cpp \ + formmanager.cpp \ + configwidgets/combodatawidget.cpp \ + dialogs/ddlpreviewdialog.cpp \ + windows/ddlhistorywindow.cpp \ + common/userinputfilter.cpp \ + datagrid/sqlqueryrownummodel.cpp \ + windows/functionseditor.cpp \ + windows/functionseditormodel.cpp \ + sqlitesyntaxhighlighter.cpp \ + windows/collationseditor.cpp \ + selectabledbmodel.cpp \ + windows/collationseditormodel.cpp \ + qtscriptsyntaxhighlighter.cpp \ + icon.cpp \ + configmapper.cpp \ + dialogs/exportdialog.cpp \ + dbobjlistmodel.cpp \ + common/verifiablewizardpage.cpp \ + selectabledbobjmodel.cpp \ + common/widgetstateindicator.cpp \ + configwidgets/listtostringlisthash.cpp \ + dialogs/versionconvertsummarydialog.cpp \ + sqlcompareview.cpp \ + dialogs/errorsconfirmdialog.cpp \ + dialogs/sortdialog.cpp \ + dialogs/importdialog.cpp \ + dialogs/populatedialog.cpp \ + dialogs/populateconfigdialog.cpp \ + common/configradiobutton.cpp \ + uiloader.cpp \ + common/fileedit.cpp \ + uiscriptingcombo.cpp \ + uiscriptingedit.cpp \ + uicustomicon.cpp \ + uiurlbutton.cpp \ + common/configcombobox.cpp \ + dialogs/dbconverterdialog.cpp \ + dialogs/dbdialog.cpp \ + uidebug.cpp \ + debugconsole.cpp \ + common/extactionprototype.cpp \ + dialogs/bugdialog.cpp \ + dialogs/aboutdialog.cpp \ + dialogs/bugreportlogindialog.cpp \ + windows/bugreporthistorywindow.cpp \ + dialogs/newversiondialog.cpp \ + dialogs/quitconfirmdialog.cpp \ + common/datawidgetmapper.cpp + +HEADERS += mainwindow.h \ + iconmanager.h \ + dbtree/dbtreemodel.h \ + dbtree/dbtreeitem.h \ + dbtree/dbtree.h \ + dbtree/dbtreeview.h \ + actionentry.h \ + uiutils.h \ + dbtree/dbtreeitemdelegate.h \ + dbtree/dbtreeitemfactory.h \ + sqleditor.h \ + datagrid/sqlquerymodel.h \ + dblistmodel.h \ + mdiarea.h \ + statusfield.h \ + common/tablewidget.h \ + datagrid/sqlqueryitem.h \ + datagrid/sqlqueryview.h \ + datagrid/sqlquerymodelcolumn.h \ + datagrid/sqlqueryitemdelegate.h \ + common/extlineedit.h \ + common/intvalidator.h \ + common/widgetcover.h \ + mdiwindow.h \ + mdichild.h \ + taskbar.h \ + multieditor/multieditor.h \ + multieditor/multieditorwidgetplugin.h \ + multieditor/multieditorwidget.h \ + widgetresizer.h \ + multieditor/multieditortext.h \ + multieditor/multieditornumeric.h \ + common/numericspinbox.h \ + multieditor/multieditordatetime.h \ + multieditor/multieditordate.h \ + multieditor/multieditortime.h \ + formview.h \ + multieditor/multieditorbool.h \ + multieditor/multieditorhex.h \ + qhexedit2/xbytearray.h \ + qhexedit2/qhexedit_p.h \ + qhexedit2/qhexedit.h \ + qhexedit2/commands.h \ + multieditor/multieditordialog.h \ + completer/completerwindow.h \ + completer/completermodel.h \ + completer/completeritemdelegate.h \ + completer/completerview.h \ + dialogs/searchtextdialog.h \ + searchtextlocator.h \ + windows/tablewindow.h \ + windows/editorwindow.h \ + datagrid/sqltablemodel.h \ + dataview.h \ + windows/tablestructuremodel.h \ + windows/tableconstraintsmodel.h \ + dialogs/columndialog.h \ + dialogs/columndialogconstraintsmodel.h \ + common/extaction.h \ + common/extactioncontainer.h \ + constraints/tableprimarykeypanel.h \ + constraints/constraintpanel.h \ + constraints/tableforeignkeypanel.h \ + constraints/tableuniquepanel.h \ + constraints/tablepkanduniquepanel.h \ + constraints/tablecheckpanel.h \ + constraints/columncheckpanel.h \ + constraints/constraintcheckpanel.h \ + constraints/columnforeignkeypanel.h \ + constraints/columnprimarykeypanel.h \ + constraints/columnuniquepanel.h \ + constraints/columnuniqueandnotnullpanel.h \ + constraints/columnnotnullpanel.h \ + constraints/columncollatepanel.h \ + constraints/columndefaultpanel.h \ + dialogs/constraintdialog.h \ + dialogs/newconstraintdialog.h \ + windows/constrainttabmodel.h \ + dialogs/messagelistdialog.h \ + windows/viewwindow.h \ + uiconfig.h \ + dialogs/indexdialog.h \ + sqlview.h \ + dialogs/triggerdialog.h \ + dialogs/triggercolumnsdialog.h \ + dbobjectdialogs.h \ + common/fontedit.h \ + customconfigwidgetplugin.h \ + configwidgets/styleconfigwidget.h \ + common/colorbutton.h \ + formmanager.h \ + configwidgets/combodatawidget.h \ + dialogs/ddlpreviewdialog.h \ + windows/ddlhistorywindow.h \ + common/userinputfilter.h \ + datagrid/sqlqueryrownummodel.h \ + windows/functionseditor.h \ + windows/functionseditormodel.h \ + syntaxhighlighterplugin.h \ + sqlitesyntaxhighlighter.h \ + windows/collationseditor.h \ + selectabledbmodel.h \ + windows/collationseditormodel.h \ + qtscriptsyntaxhighlighter.h \ + icon.h \ + configmapper.h \ + dialogs/exportdialog.h \ + dbobjlistmodel.h \ + common/verifiablewizardpage.h \ + selectabledbobjmodel.h \ + common/widgetstateindicator.h \ + configwidgets/listtostringlisthash.h \ + dialogs/versionconvertsummarydialog.h \ + sqlcompareview.h \ + dialogs/errorsconfirmdialog.h \ + dialogs/sortdialog.h \ + dialogs/importdialog.h \ + dialogs/populatedialog.h \ + dialogs/populateconfigdialog.h \ + common/configradiobutton.h \ + uiloader.h \ + common/fileedit.h \ + uiscriptingcombo.h \ + uiloaderpropertyhandler.h \ + uiscriptingedit.h \ + uicustomicon.h \ + uiurlbutton.h \ + common/configcombobox.h \ + dialogs/dbconverterdialog.h \ + dialogs/configdialog.h \ + dialogs/dbdialog.h \ + uidebug.h \ + debugconsole.h \ + common/extactionprototype.h \ + dialogs/bugdialog.h \ + dialogs/aboutdialog.h \ + dialogs/bugreportlogindialog.h \ + windows/bugreporthistorywindow.h \ + dialogs/newversiondialog.h \ + guiSQLiteStudio_global.h \ + dialogs/quitconfirmdialog.h \ + common/datawidgetmapper.h + +FORMS += mainwindow.ui \ + dbtree/dbtree.ui \ + statusfield.ui \ + completer/completerwindow.ui \ + dialogs/searchtextdialog.ui \ + windows/tablewindow.ui \ + windows/editorwindow.ui \ + dialogs/columndialog.ui \ + constraints/tableforeignkeypanel.ui \ + constraints/tablepkanduniquepanel.ui \ + constraints/constraintcheckpanel.ui \ + constraints/columnforeignkeypanel.ui \ + constraints/columnprimarykeypanel.ui \ + constraints/columnuniqueandnotnullpanel.ui \ + constraints/columncollatepanel.ui \ + constraints/columndefaultpanel.ui \ + dialogs/constraintdialog.ui \ + dialogs/newconstraintdialog.ui \ + dialogs/messagelistdialog.ui \ + windows/viewwindow.ui \ + dialogs/configdialog.ui \ + dialogs/indexdialog.ui \ + dialogs/triggerdialog.ui \ + dialogs/triggercolumnsdialog.ui \ + common/fontedit.ui \ + forms/sqlformatterplugin.ui \ + dialogs/ddlpreviewdialog.ui \ + windows/ddlhistorywindow.ui \ + windows/functionseditor.ui \ + windows/collationseditor.ui \ + dialogs/exportdialog.ui \ + dialogs/versionconvertsummarydialog.ui \ + dialogs/errorsconfirmdialog.ui \ + dialogs/sortdialog.ui \ + dialogs/importdialog.ui \ + dialogs/populatedialog.ui \ + dialogs/populateconfigdialog.ui \ + dialogs/dbconverterdialog.ui \ + dialogs/dbdialog.ui \ + debugconsole.ui \ + dialogs/bugdialog.ui \ + dialogs/aboutdialog.ui \ + dialogs/bugreportlogindialog.ui \ + windows/bugreporthistorywindow.ui \ + dialogs/newversiondialog.ui \ + dialogs/quitconfirmdialog.ui + +RESOURCES += \ + icons.qrc + +OTHER_FILES += + +LIBS += -lcoreSQLiteStudio diff --git a/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h new file mode 100644 index 0000000..9e1992e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio_global.h @@ -0,0 +1,12 @@ +#ifndef GUISQLITESTUDIO_GLOBAL_H +#define GUISQLITESTUDIO_GLOBAL_H + +#include + +#if defined(GUISQLITESTUDIO_LIBRARY) +# define GUI_API_EXPORT Q_DECL_EXPORT +#else +# define GUI_API_EXPORT Q_DECL_IMPORT +#endif + +#endif // GUISQLITESTUDIO_GLOBAL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/icon.cpp b/SQLiteStudio3/guiSQLiteStudio/icon.cpp new file mode 100644 index 0000000..bdc21ce --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/icon.cpp @@ -0,0 +1,385 @@ +#include "icon.h" +#include "iconmanager.h" +#include "common/global.h" +#include +#include +#include +#include + +QHash Icon::instances; + + +Icon::Icon(const QString& name, const QString& fileName) : + name(name) +{ + this->fileName = fileName; + instances[name] = this; +} + +Icon::Icon(const Icon& other) : + loaded(other.loaded), movie(other.movie), name(other.name), attr(other.attr), filePath(other.filePath), copyFrom(other.copyFrom), + aliased(other.aliased), movieHandle(other.movieHandle), iconHandle(other.iconHandle) +{ + instances[name] = this; +} + +Icon::Icon(const QString& name) : + name(name) +{ + instances[name] = this; +} + +Icon::~Icon() +{ + for (QIcon* icon : dynamicallyAttributed.values()) + delete icon; + + dynamicallyAttributed.clear(); + + safe_delete(iconHandle); + safe_delete(movieHandle); +} + +void Icon::load() +{ + if (aliased) + { + aliased->load(); + return; + } + + if (loaded) + return; + + if (copyFrom) // currently copyFrom works only on icons, not movies + { + if (!copyFrom->loaded) + copyFrom->load(); + + // Get base icon + QIcon* icon = copyFrom->toQIconPtr(); + if (!icon) + { + qWarning() << "No QIcon in icon to copy from, while copying icon named" << copyFrom->name; + return; + } + + iconHandle = new QIcon(mergeAttribute(icon, attr)); + } + else + { + filePath = IconManager::getInstance()->getFilePathForName(fileName); + if (!filePath.isNull()) + { + if (IconManager::getInstance()->isMovie(fileName)) + movieHandle = IconManager::getInstance()->getMovie(fileName); + else + iconHandle = IconManager::getInstance()->getIcon(fileName); + } + else + qWarning() << "No file path for icon" << name; + } + + loaded = true; +} + +QString Icon::toImgSrc() const +{ + if (aliased) + return aliased->toImgSrc(); + + if (!filePath.isNull()) + return getPath(); + else + return toBase64Url(); +} + +QString Icon::toBase64Url() const +{ + static const QString urlTempl = QStringLiteral("data:image/png;base64,%1"); + return urlTempl.arg(QString(toBase64())); +} + +QByteArray Icon::toBase64() const +{ + return toPixmapBytes().toBase64(); +} + +QByteArray Icon::toPixmapBytes() const +{ + if (aliased) + return aliased->toPixmapBytes(); + + QByteArray byteArray; + if (!loaded) + { + qCritical() << "Referring to a movie that was not yet loaded:" << name; + return byteArray; + } + + QBuffer buffer(&byteArray); + iconHandle->pixmap(16, 16).save(&buffer, "PNG"); + return byteArray; +} + +QString Icon::toUrl() const +{ + if (aliased) + return aliased->toUrl(); + + if (filePath.isNull()) + return toBase64Url(); + + return filePath; +} + +QIcon* Icon::toQIconPtr() const +{ + if (aliased) + return aliased->toQIconPtr(); + + if (!loaded) + { + qCritical() << "Referring to an icon that was not yet loaded:" << name; + return nullptr; + } + + return iconHandle; +} + +QIcon Icon::toQIcon() const +{ + return *toQIconPtr(); +} + +Icon* Icon::toIconPtr() +{ + return this; +} + +QPixmap Icon::toQPixmap() const +{ + return toQIconPtr()->pixmap(16, 16); +} + +QMovie* Icon::toQMoviePtr() const +{ + if (aliased) + return aliased->toQMoviePtr(); + + if (!loaded) + { + qCritical() << "Referring to a movie that was not yet loaded:" << name; + return nullptr; + } + + if (!movieHandle) + return nullptr; // this is not a movie + + if (movieHandle->state() != QMovie::Running) + movieHandle->start(); + + return movieHandle; +} + +QVariant Icon::toQVariant() const +{ + return QVariant::fromValue(operator QIcon()); +} + +QIcon* Icon::with(Icon::Attributes attr) +{ + if (dynamicallyAttributed.contains(attr)) + return dynamicallyAttributed[attr]; + + if (aliased) + return aliased->with(attr); + + if (!loaded) + { + qCritical() << "Referring to a icon that was not yet loaded:" << name; + return nullptr; + } + + if (movieHandle) + return nullptr; // this is a movie + + QIcon* merged = new QIcon(mergeAttribute(iconHandle, attr)); + dynamicallyAttributed[attr] = merged; + return merged; +} + +Icon::operator Icon*() +{ + return this; +} + +void Icon::init() +{ + qRegisterMetaType(); + qRegisterMetaTypeStreamOperators(); +} + +QString Icon::getFileName() const +{ + return fileName; +} + +QString Icon::getName() const +{ + return name; +} + +QString Icon::getPath() const +{ + if (aliased) + aliased->getPath(); + + return filePath; +} + +bool Icon::isNull() const +{ + if (aliased) + return aliased->isNull(); + + return (!iconHandle || iconHandle->isNull()) && !movieHandle; +} + +bool Icon::isMovie() const +{ + if (aliased) + return aliased->isMovie(); + + return movieHandle != nullptr; +} + +Icon& Icon::createFrom(const QString& name, Icon* copy, Icon::Attributes attr) +{ + Icon* newIcon = new Icon(name); + newIcon->copyFrom = copy; + newIcon->attr = attr; + newIcon->name = name; + + return *newIcon; +} + +Icon& Icon::aliasOf(const QString& name, Icon* other) +{ + Icon* newIcon = new Icon(name); + newIcon->aliased = other; + newIcon->name = name; + return *newIcon; +} + +QIcon Icon::merge(const QIcon& icon, Icon::Attributes attr) +{ + return mergeAttribute(&icon, attr); +} + +void Icon::loadAll() +{ + for (Icon* icon : instances.values()) + icon->load(); +} + +void Icon::reloadAll() +{ + for (Icon* icon : instances.values()) + { + icon->loaded = false; + icon->load(); + } +} + +QString Icon::getIconNameForAttribute(Icon::Attributes attr) +{ + switch (attr) + { + case PLUS: + return "plus_small"; + case MINUS: + return "minus_small"; + case EDIT: + return "edit_small"; + case DELETE: + return "delete_small"; + case DENIED: + return "denied_small"; + case INFO: + return "info_small"; + case WARNING: + return "warn_small"; + case QUESTION: + return "question_small"; + case ERROR: + return "error_small"; + case SORT_ASC: + return "sort_ind_asc"; + case SORT_DESC: + return "sort_ind_desc"; + default: + qWarning() << "Unhandled icon attribute:" << attr; + } + return QString::null; +} + +QIcon Icon::mergeAttribute(const QIcon* icon, Icon::Attributes attr) +{ + QString attribName = getIconNameForAttribute(attr); + QIcon* attrIcon = IconManager::getInstance()->getIcon(attribName); + if (!attrIcon) + { + qWarning() << "No attribute icon for attribute:" << attribName; + return *icon; + } + + // Merge icons + QPixmap attrPixmap = attrIcon->pixmap(16, 16); + QPixmap newPixmap = icon->pixmap(16, 16); + + QPainter painter(&newPixmap); + painter.drawPixmap(0, 0, attrPixmap); + + // Create new icon + return QIcon(newPixmap); +} + +Icon::operator QVariant() const +{ + return toQVariant(); +} + +Icon::operator QMovie*() const +{ + return toQMoviePtr(); +} + +Icon::operator QIcon*() const +{ + return toQIconPtr(); +} + +Icon::operator QPixmap() const +{ + return toQPixmap(); +} + +Icon::operator QIcon() const +{ + return toQIcon(); +} + +QDataStream& operator<<(QDataStream& out, const Icon* icon) +{ + out << reinterpret_cast(icon); + return out; +} + +QDataStream& operator>>(QDataStream& in, const Icon*& icon) +{ + qint64 ptr; + in >> ptr; + icon = reinterpret_cast(ptr); + return in; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/icon.h b/SQLiteStudio3/guiSQLiteStudio/icon.h new file mode 100644 index 0000000..d6512c9 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/icon.h @@ -0,0 +1,104 @@ +#ifndef ICONS_H +#define ICONS_H + +#include "guiSQLiteStudio_global.h" +#include +#include +#include + +class QMovie; + +#define DEF_ICONS(TypeName, ObjName, Defs) \ + struct GUI_API_EXPORT TypeName \ + { \ + Defs \ + }; \ + TypeName ObjName; + +#define DEF_ICON(E,N) Icon E = Icon(#E, N); +#define DEF_ICO2(E,Src,Attr) Icon E = Icon::createFrom(#E, Src, Icon::Attr); +#define DEF_ICO3(E,Src) Icon E = Icon::aliasOf(#E, &Src); + +class GUI_API_EXPORT Icon +{ + public: + enum Attributes + { + NONE, + PLUS, + MINUS, + EDIT, + DELETE, + DENIED, + INFO, + WARNING, + QUESTION, + ERROR, + SORT_ASC, + SORT_DESC + }; + + Icon(const QString& name, const QString& fileName); + Icon(const Icon& other); + ~Icon(); + + QString getFileName() const; + QString getName() const; + QString getPath() const; + bool isNull() const; + bool isMovie() const; + void load(); + QString toImgSrc() const; + QString toBase64Url() const; + QByteArray toBase64() const; + QByteArray toPixmapBytes() const; + QString toUrl() const; + QIcon* toQIconPtr() const; + QIcon toQIcon() const; + Icon* toIconPtr(); + QPixmap toQPixmap() const; + QMovie* toQMoviePtr() const; + QVariant toQVariant() const; + QIcon* with(Attributes attr); + + operator Icon*(); + operator QIcon() const; + operator QIcon*() const; + operator QPixmap() const; + operator QMovie*() const; + operator QVariant() const; + + static void init(); + static void loadAll(); + static void reloadAll(); + static Icon& createFrom(const QString& name, Icon* copy, Attributes attr); + static Icon& aliasOf(const QString& name, Icon* other); + static QIcon merge(const QIcon& icon, Attributes attr); + + private: + explicit Icon(const QString& name); + + static QString getIconNameForAttribute(Attributes attr); + static QIcon mergeAttribute(const QIcon* icon, Attributes attr); + + bool loaded = false; + bool movie = false; + QString name; + Attributes attr = NONE; + QString fileName; + QString filePath; + Icon* copyFrom = nullptr; + Icon* aliased = nullptr; + QMovie* movieHandle = nullptr; + QIcon* iconHandle = nullptr; + QHash dynamicallyAttributed; + + static QHash instances; +}; + +GUI_API_EXPORT QDataStream &operator<<(QDataStream &out, const Icon* icon); +GUI_API_EXPORT QDataStream &operator>>(QDataStream &in, const Icon*& icon); + +Q_DECLARE_METATYPE(const Icon*) + +#endif // ICONS_H diff --git a/SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp b/SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp new file mode 100644 index 0000000..efe22a1 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/iconmanager.cpp @@ -0,0 +1,164 @@ +#include "iconmanager.h" +#include "sqlitestudio.h" +#include "services/pluginmanager.h" +#include "common/unused.h" +#include "common/global.h" +#include +#include +#include +#include +#include +#include +#include + +IconManager* IconManager::instance = nullptr; + +IconManager* IconManager::getInstance() +{ + if (instance == nullptr) + instance = new IconManager(); + + return instance; +} + +QString IconManager::getFilePathForName(const QString& name) +{ + return paths[name]; +} + +IconManager::IconManager() +{ +} + +void IconManager::init() +{ + Icon::init(); + + iconDirs += qApp->applicationDirPath() + "/img"; + iconDirs += ":/icons"; + + QString envDirs = SQLITESTUDIO->getEnv("SQLITESTUDIO_ICONS"); + if (!envDirs.isNull()) + iconDirs += envDirs.split(PATH_LIST_SEPARATOR); + +#ifdef ICONS_DIR + iconDirs += STRINGIFY(ICONS_DIR); +#endif + + iconFileExtensions << "*.png" << "*.PNG" << "*.jpg" << "*.JPG" << "*.svg" << "*.SVG"; + movieFileExtensions << "*.gif" << "*.GIF" << "*.mng" << "*.MNG"; + + foreach (QString dirPath, iconDirs) + { + loadRecurently(dirPath, "", false); + loadRecurently(dirPath, "", true); + } + + Icon::loadAll(); + + if (PLUGINS->arePluginsInitiallyLoaded()) + enableRescanning(); + else + connect(PLUGINS, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(pluginsInitiallyLoaded())); +} + +void IconManager::rescanResources(const QString& pluginName) +{ + if (!pluginName.isNull() && PLUGINS->isBuiltIn(pluginName)) + return; + + for (const QString& name : resourceMovies) + { + delete movies[name]; + movies.remove(name); + } + + for (const QString& name : resourceIcons) + icons.remove(name); + + resourceMovies.clear(); + resourceIcons.clear(); + loadRecurently(":/icons", "", true); + loadRecurently(":/icons", "", false); + + Icon::reloadAll(); +} + +void IconManager::rescanResources(Plugin* plugin, PluginType* pluginType) +{ + UNUSED(pluginType); + rescanResources(plugin->getName()); +} + +void IconManager::pluginsAboutToMassUnload() +{ + disconnect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(rescanResources(Plugin*,PluginType*))); + disconnect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(rescanResources(QString))); +} + +void IconManager::pluginsInitiallyLoaded() +{ + Icon::reloadAll(); + enableRescanning(); + disconnect(PLUGINS, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(pluginsInitiallyLoaded())); +} + +void IconManager::loadRecurently(QString dirPath, const QString& prefix, bool movie) +{ + QStringList extensions = movie ? movieFileExtensions : iconFileExtensions; + QString path; + QString name; + QDir dir(dirPath); + foreach (QFileInfo entry, dir.entryInfoList(extensions, QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot|QDir::Readable)) + { + if (entry.isDir()) + { + loadRecurently(entry.absoluteFilePath(), prefix+entry.fileName()+"_", movie); + continue; + } + + path = entry.absoluteFilePath(); + name = entry.baseName(); + paths[name] = path; + if (movie) + movies[name] = new QMovie(path); + else + icons[name] = new QIcon(path); + + if (path.startsWith(":/")) + { + if (movie) + resourceMovies << name; + else + resourceIcons << name; + } + } +} + +void IconManager::enableRescanning() +{ + connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(rescanResources(Plugin*,PluginType*))); + connect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(rescanResources(QString))); + connect(PLUGINS, SIGNAL(aboutToQuit()), this, SLOT(pluginsAboutToMassUnload())); +} + +QMovie* IconManager::getMovie(const QString& name) +{ + if (!movies.contains(name)) + qCritical() << "Movie missing:" << name; + + return movies[name]; +} + +QIcon* IconManager::getIcon(const QString& name) +{ + if (!icons.contains(name)) + qCritical() << "Icon missing:" << name; + + return icons[name]; +} + +bool IconManager::isMovie(const QString& name) +{ + return movies.contains(name); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/iconmanager.h b/SQLiteStudio3/guiSQLiteStudio/iconmanager.h new file mode 100644 index 0000000..8cb7dbe --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/iconmanager.h @@ -0,0 +1,275 @@ +#ifndef ICONMANAGER_H +#define ICONMANAGER_H + +#include "icon.h" +#include "guiSQLiteStudio_global.h" +#include +#include +#include +#include + +class QMovie; +class PluginType; +class Plugin; + +class GUI_API_EXPORT IconManager : public QObject +{ + Q_OBJECT + + public: + DEF_ICONS(Icons, iconEnums, + DEF_ICON(ABORT24, "abort24") + DEF_ICON(ACT_ABORT, "act_abort") + DEF_ICON(ACT_CLEAR, "act_clear") + DEF_ICON(ACT_COPY, "act_copy") + DEF_ICON(ACT_CUT, "act_cut") + DEF_ICON(ACT_DEL_LINE, "act_del_line") + DEF_ICON(ACT_DELETE, "act_delete") + DEF_ICON(ACT_PASTE, "act_paste") + DEF_ICON(ACT_REDO, "act_redo") + DEF_ICON(ACT_SEARCH, "act_search") + DEF_ICON(ACT_SELECT_ALL, "act_select_all") + DEF_ICON(ACT_UNDO, "act_undo") + DEF_ICON(APPLY_FILTER, "apply_filter") + DEF_ICON(APPLY_FILTER_RE, "apply_filter_re") + DEF_ICON(APPLY_FILTER_SQL, "apply_filter_sql") + DEF_ICON(APPLY_FILTER_TXT, "apply_filter_txt") + DEF_ICON(BUG, "bug") + DEF_ICON(BUG_LIST, "bug_list") + DEF_ICON(CLEAR_HISTORY, "clear_history") + DEF_ICON(CLEAR_LINEEDIT, "clear_lineedit") + DEF_ICON(CLOSE, "close") + DEF_ICON(COLUMN, "column") + DEF_ICON(COLUMN_CONSTRAINT, "column_constraint") + DEF_ICO2(COLUMN_CONSTRAINT_ADD, COLUMN_CONSTRAINT, PLUS) + DEF_ICO2(COLUMN_CONSTRAINT_DEL, COLUMN_CONSTRAINT, MINUS) + DEF_ICO2(COLUMN_CONSTRAINT_EDIT, COLUMN_CONSTRAINT, EDIT) + DEF_ICON(COLUMNS, "columns") + DEF_ICON(COMMIT, "commit") + DEF_ICON(COMPLETE, "complete") + DEF_ICON(COMPLETER_BLOB, "completer_blob") + DEF_ICON(COMPLETER_NO_VALUE, "completer_no_value") + DEF_ICON(COMPLETER_NUMBER, "completer_number") + DEF_ICON(COMPLETER_OPERATOR, "completer_operator") + DEF_ICON(COMPLETER_OTHER, "completer_other") + DEF_ICON(COMPLETER_PRAGMA, "completer_pragma") + DEF_ICON(COMPLETER_STRING, "completer_string") + DEF_ICON(CONFIGURE, "configure") + DEF_ICON(CONFIGURE_CONSTRAINT, "configure_constraint") + DEF_ICON(CONSTRAINT_CHECK, "check") + DEF_ICO2(CONSTRAINT_CHECK_ADD, CONSTRAINT_CHECK, PLUS) + DEF_ICON(CONSTRAINT_COLLATION, "collation") + DEF_ICO2(CONSTRAINT_COLLATION_ADD, CONSTRAINT_COLLATION, PLUS) + DEF_ICON(CONSTRAINT_DEFAULT, "default") + DEF_ICO2(CONSTRAINT_DEFAULT_ADD, CONSTRAINT_DEFAULT, PLUS) + DEF_ICON(CONSTRAINT_FOREIGN_KEY, "fk") + DEF_ICO2(CONSTRAINT_FOREIGN_KEY_ADD, CONSTRAINT_FOREIGN_KEY, PLUS) + DEF_ICON(CONSTRAINT_NOT_NULL, "not_null") + DEF_ICO2(CONSTRAINT_NOT_NULL_ADD, CONSTRAINT_NOT_NULL, PLUS) + DEF_ICON(CONSTRAINT_PRIMARY_KEY, "pk") + DEF_ICO2(CONSTRAINT_PRIMARY_KEY_ADD, CONSTRAINT_PRIMARY_KEY, PLUS) + DEF_ICON(CONSTRAINT_UNIQUE, "unique") + DEF_ICO2(CONSTRAINT_UNIQUE_ADD, CONSTRAINT_UNIQUE, PLUS) + DEF_ICON(CONVERT_DB, "convert_db") + DEF_ICON(VACUUM_DB, "vacuum_db") + DEF_ICON(INTEGRITY_CHECK, "integrity_check") + DEF_ICON(DATABASE, "database") + DEF_ICO2(DATABASE_ADD, DATABASE, PLUS) + DEF_ICON(DATABASE_CONNECT, "database_connect") + DEF_ICON(DATABASE_CONNECTED, "database_connected") + DEF_ICO2(DATABASE_DEL, DATABASE, MINUS) + DEF_ICON(DATABASE_DISCONNECT, "database_disconnect") + DEF_ICO2(DATABASE_EDIT, DATABASE, EDIT) + DEF_ICON(DATABASE_EXPORT, "database_export") + DEF_ICON(DATABASE_EXPORT_WIZARD, "database_export_wizard") + DEF_ICON(DATABASE_FILE, "database_file") + DEF_ICON(DATABASE_IMPORT_WIZARD, "database_import_wizard") + DEF_ICO2(DATABASE_INVALID, DATABASE, WARNING) + DEF_ICON(DATABASE_NETWORK, "database_network") + DEF_ICON(DATABASE_OFFLINE, "database_offline") + DEF_ICON(DATABASE_ONLINE, "database_online") + DEF_ICON(DATABASE_RELOAD, "database_reload") + DEF_ICON(DDL_HISTORY, "ddl_history") + DEF_ICON(DELETE_ROW, "delete_row") + DEF_ICO3(DELETE_COLLATION, DELETE_ROW) + DEF_ICO3(DELETE_DATATYPE, DELETE_ROW) + DEF_ICO3(DELETE_FN_ARG, DELETE_ROW) + DEF_ICO3(DELETE_FUNCTION, DELETE_ROW) + DEF_ICON(DELETE_SELECTED, "delete_selected") + DEF_ICON(DIRECTORY, "directory") + DEF_ICO2(DIRECTORY_ADD, DIRECTORY, PLUS) + DEF_ICO2(DIRECTORY_DEL, DIRECTORY, MINUS) + DEF_ICO2(DIRECTORY_EDIT, DIRECTORY, EDIT) + DEF_ICON(DIRECTORY_OPEN, "directory_open") + DEF_ICON(DIRECTORY_OPEN_WITH_DB, "directory_open_with_db") + DEF_ICON(DIRECTORY_WITH_DB, "directory_with_db") + DEF_ICON(ERASE, "erase") + DEF_ICON(EXEC_QUERY, "exec_query") + DEF_ICON(EXPLAIN_QUERY, "explain_query") + DEF_ICON(EXPORT, "export") + DEF_ICON(EXPORT_FILE_BROWSE, "export_file_browse") + DEF_ICON(FEATURE_REQUEST, "feature_request") + DEF_ICON(FONT_BROWSE, "font_browse") + DEF_ICON(FORMAT_SQL, "format_sql") + DEF_ICON(FUNCTION, "function") + DEF_ICON(GET_UPDATE, "get_update") + DEF_ICON(GO_BACK, "go_back") + DEF_ICON(HELP, "help") + DEF_ICON(HOMEPAGE, "homepage") + DEF_ICON(IMPORT, "import") + DEF_ICON(INDEX, "index") + DEF_ICO2(INDEX_ADD, INDEX, PLUS) + DEF_ICO2(INDEX_DEL, INDEX, MINUS) + DEF_ICO2(INDEX_EDIT, INDEX, EDIT) + DEF_ICON(INDEXES, "indexes") + DEF_ICON(INDICATOR_ERROR, "indicator_error") + DEF_ICON(INDICATOR_HINT, "indicator_hint") + DEF_ICON(INDICATOR_INFO, "indicator_info") + DEF_ICON(INDICATOR_WARN, "indicator_warn") + DEF_ICON(INFO_BALLOON, "info_balloon") + DEF_ICON(INSERT_ROW, "insert_row") + DEF_ICON(INSERT_ROWS, "insert_rows") + DEF_ICO3(INSERT_FN_ARG, INSERT_ROW) + DEF_ICO3(INSERT_DATATYPE, INSERT_ROW) + DEF_ICON(KEYWORD, "keyword") + DEF_ICON(KEYBOARD, "keyboard") + DEF_ICON(LOADING, "loading") + DEF_ICON(LICENSES, "licenses") + DEF_ICON(MOVE_DOWN, "move_down") + DEF_ICON(MOVE_UP, "move_up") + DEF_ICO3(NEW_COLLATION, INSERT_ROW) + DEF_ICO3(NEW_FUNCTION, INSERT_ROW) + DEF_ICON(OPEN_FILE, "open_sql_file") + DEF_ICON(OPEN_FORUM, "open_forum") + DEF_ICON(OPEN_SQL_EDITOR, "open_sql_editor") + DEF_ICO3(OPEN_SQL_FILE, OPEN_FILE) + DEF_ICON(OPEN_VALUE_EDITOR, "open_value_editor") + DEF_ICON(PAGE_FIRST, "page_first") + DEF_ICON(PAGE_LAST, "page_last") + DEF_ICON(PAGE_NEXT, "page_next") + DEF_ICON(PAGE_PREV, "page_prev") + DEF_ICO3(MOVE_LEFT, PAGE_PREV) + DEF_ICO3(MOVE_RIGHT, PAGE_NEXT) + DEF_ICON(RELOAD, "reload") + DEF_ICON(RENAME_FN_ARG, "rename_fn_arg") + DEF_ICO3(RENAME_DATATYPE, RENAME_FN_ARG) + DEF_ICON(RESULTS_BELOW, "results_below") + DEF_ICON(RESULTS_IN_TAB, "results_in_tab") + DEF_ICON(ROLLBACK, "rollback") + DEF_ICON(SAVE_SQL_FILE, "save_sql_file") + DEF_ICON(SET_NULL, "set_null") + DEF_ICON(SORT_COLUMNS, "sort_columns") + DEF_ICON(SORT_COUNT_01, "sort_cnt_01") + DEF_ICON(SORT_COUNT_02, "sort_cnt_02") + DEF_ICON(SORT_COUNT_03, "sort_cnt_03") + DEF_ICON(SORT_COUNT_04, "sort_cnt_04") + DEF_ICON(SORT_COUNT_05, "sort_cnt_05") + DEF_ICON(SORT_COUNT_06, "sort_cnt_06") + DEF_ICON(SORT_COUNT_07, "sort_cnt_07") + DEF_ICON(SORT_COUNT_08, "sort_cnt_08") + DEF_ICON(SORT_COUNT_09, "sort_cnt_09") + DEF_ICON(SORT_COUNT_10, "sort_cnt_10") + DEF_ICON(SORT_COUNT_11, "sort_cnt_11") + DEF_ICON(SORT_COUNT_12, "sort_cnt_12") + DEF_ICON(SORT_COUNT_13, "sort_cnt_13") + DEF_ICON(SORT_COUNT_14, "sort_cnt_14") + DEF_ICON(SORT_COUNT_15, "sort_cnt_15") + DEF_ICON(SORT_COUNT_16, "sort_cnt_16") + DEF_ICON(SORT_COUNT_17, "sort_cnt_17") + DEF_ICON(SORT_COUNT_18, "sort_cnt_18") + DEF_ICON(SORT_COUNT_19, "sort_cnt_19") + DEF_ICON(SORT_COUNT_20, "sort_cnt_20") + DEF_ICON(SORT_COUNT_20_PLUS, "sort_cnt_20p") + DEF_ICON(SORT_INDICATOR_ASC, "sort_ind_asc") + DEF_ICON(SORT_INDICATOR_DESC, "sort_ind_desc") + DEF_ICON(SORT_RESET, "sort_reset") + DEF_ICON(SQLITE_DOCS, "sqlite_docs") + DEF_ICON(SQLITESTUDIO_APP, "sqlitestudio") + DEF_ICON(SQLITESTUDIO_APP16, "sqlitestudio_16") + DEF_ICON(STATUS_ERROR, "status_error") + DEF_ICON(STATUS_INFO, "status_info") + DEF_ICON(STATUS_WARNING, "status_warn") + DEF_ICON(TABLE, "table") + DEF_ICO2(TABLE_ADD, TABLE, PLUS) + DEF_ICON(TABLE_COLUMN_ADD, "table_column_add") + DEF_ICON(TABLE_COLUMN_DELETE, "table_column_delete") + DEF_ICON(TABLE_COLUMN_EDIT, "table_column_edit") + DEF_ICON(TABLE_CONSTRAINT, "table_constraint") + DEF_ICO2(TABLE_CONSTRAINT_ADD, TABLE_CONSTRAINT, PLUS) + DEF_ICO2(TABLE_CONSTRAINT_DELETE, TABLE_CONSTRAINT, MINUS) + DEF_ICO2(TABLE_CONSTRAINT_EDIT, TABLE_CONSTRAINT, EDIT) + DEF_ICON(TABLE_CREATE_SIMILAR, "table_create_similar") + DEF_ICO2(TABLE_DEL, TABLE, MINUS) + DEF_ICO2(TABLE_EDIT, TABLE, EDIT) + DEF_ICON(TABLE_EXPORT, "table_export") + DEF_ICON(TABLE_IMPORT, "table_import") + DEF_ICON(TABLE_POPULATE, "table_populate") + DEF_ICON(TABLES, "tables") + DEF_ICON(TABS_AT_BOTTOM, "tabs_at_bottom") + DEF_ICON(TABS_ON_TOP, "tabs_on_top") + DEF_ICON(TEST_CONN_ERROR, "test_conn_error") + DEF_ICON(TEST_CONN_OK, "test_conn_ok") + DEF_ICON(TIP, "tip") + DEF_ICON(TRIGGER, "trigger") + DEF_ICO2(TRIGGER_ADD, TRIGGER, PLUS) + DEF_ICON(TRIGGER_COLUMNS, "trigger_columns") + DEF_ICO2(TRIGGER_COLUMNS_INVALID, TRIGGER_COLUMNS, WARNING) + DEF_ICO2(TRIGGER_DEL, TRIGGER, MINUS) + DEF_ICO2(TRIGGER_EDIT, TRIGGER, EDIT) + DEF_ICON(TRIGGERS, "triggers") + DEF_ICON(USER, "user") + DEF_ICON(USER_UNKNOWN, "user_unknown") + DEF_ICON(USER_MANUAL, "user_manual") + DEF_ICON(VIEW, "view") + DEF_ICO2(VIEW_ADD, VIEW, PLUS) + DEF_ICO2(VIEW_DEL, VIEW, MINUS) + DEF_ICO2(VIEW_EDIT, VIEW, EDIT) + DEF_ICON(VIEWS, "views") + DEF_ICON(VIRTUAL_TABLE, "virtual_table") + DEF_ICON(WIN_CASCADE, "win_cascade") + DEF_ICON(WIN_TILE, "win_tile") + DEF_ICON(WIN_TILE_HORIZONTAL, "win_tile_horizontal") + DEF_ICON(WIN_TILE_VERTICAL, "win_tile_vertical") + DEF_ICON(WIN_CLOSE, "window_close") + DEF_ICON(WIN_CLOSE_ALL, "window_close_all") + DEF_ICON(WIN_CLOSE_OTHER, "window_close_other") + DEF_ICON(WIN_RESTORE, "window_restore") + DEF_ICON(WIN_RENAME, "window_rename") + ) + + static IconManager* getInstance(); + + QString getFilePathForName(const QString& name); + bool isMovie(const QString& name); + QMovie* getMovie(const QString& name); + QIcon* getIcon(const QString& name); + void init(); + + private: + IconManager(); + void loadRecurently(QString dirPath, const QString& prefix, bool movie); + void enableRescanning(); + + static IconManager* instance; + QHash icons; + QHash movies; + QHash paths; + QStringList iconDirs; + QStringList iconFileExtensions; + QStringList movieFileExtensions; + QStringList resourceIcons; + QStringList resourceMovies; + + private slots: + void rescanResources(Plugin* plugin, PluginType* pluginType); + void pluginsAboutToMassUnload(); + void pluginsInitiallyLoaded(); + + public slots: + void rescanResources(const QString& pluginName = QString()); +}; + +#define ICONMANAGER IconManager::getInstance() +#define ICONS ICONMANAGER->iconEnums + +#endif // ICONMANAGER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/icons.qrc b/SQLiteStudio3/guiSQLiteStudio/icons.qrc new file mode 100644 index 0000000..f923fe9 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/icons.qrc @@ -0,0 +1,194 @@ + + + img/act_abort.png + img/act_clear.png + img/act_copy.png + img/act_cut.png + img/act_del_line.png + img/act_delete.png + img/act_paste.png + img/act_redo.png + img/act_search.png + img/act_select_all.png + img/act_undo.png + img/apply_filter_re.png + img/apply_filter_sql.png + img/apply_filter_txt.png + img/apply_filter.png + img/check.png + img/clear_history.png + img/clear_lineedit.png + img/collation.png + img/column_constraint.png + img/column.png + img/columns.png + img/commit.png + img/complete.png + img/completer_blob.png + img/completer_no_value.png + img/completer_number.png + img/completer_operator.png + img/completer_other.png + img/completer_pragma.png + img/completer_string.png + img/configure_constraint.png + img/configure.png + img/database_connect.png + img/database_connected.png + img/database_disconnect.png + img/database_export.png + img/database_file.png + img/database_invalid.png + img/database_network.png + img/database_reload.png + img/database.png + img/ddl_history.png + img/default.png + img/delete_row.png + img/delete_selected.png + img/delete_small.png + img/denied_small.png + img/directory_open_with_db.png + img/directory_open.png + img/directory_with_db.png + img/directory.png + img/edit_small.png + img/erase.png + img/error_small.png + img/exec_query.png + img/explain_query.png + img/export_file_browse.png + img/export.png + img/fk.png + img/font_browse.png + img/format_sql.png + img/function.png + img/help.png + img/import.png + img/index.png + img/indexes.png + img/indicator_error.png + img/indicator_hint.png + img/indicator_info.png + img/indicator_warn.png + img/info_balloon.png + img/info_small.png + img/insert_row.png + img/insert_rows.png + img/keyword.png + img/loading.gif + img/minus_small.png + img/move_down.png + img/move_up.png + img/not_null.png + img/open_sql_editor.png + img/open_sql_file.png + img/open_value_editor.png + img/page_first.png + img/page_last.png + img/page_next.png + img/page_prev.png + img/pk.png + img/plus_small.png + img/question_small.png + img/reload.png + img/rename_fn_arg.png + img/results_below.png + img/results_in_tab.png + img/rollback.png + img/save_sql_file.png + img/set_null.png + img/sort_cnt_01.png + img/sort_cnt_02.png + img/sort_cnt_03.png + img/sort_cnt_04.png + img/sort_cnt_05.png + img/sort_cnt_06.png + img/sort_cnt_07.png + img/sort_cnt_08.png + img/sort_cnt_09.png + img/sort_cnt_10.png + img/sort_cnt_11.png + img/sort_cnt_12.png + img/sort_cnt_13.png + img/sort_cnt_14.png + img/sort_cnt_15.png + img/sort_cnt_16.png + img/sort_cnt_17.png + img/sort_cnt_18.png + img/sort_cnt_19.png + img/sort_cnt_20.png + img/sort_cnt_20p.png + img/sort_columns.png + img/sort_ind_asc.png + img/sort_ind_desc.png + img/sort_reset.png + img/sql.png + img/status_error.png + img/status_info.png + img/status_warn.png + img/table_column_add.png + img/table_column_delete.png + img/table_column_edit.png + img/table_constraint.png + img/table_create_similar.png + img/table_export.png + img/table_import.png + img/table_populate.png + img/table.png + img/tables.png + img/tabs_at_bottom.png + img/tabs_on_top.png + img/test_conn_error.png + img/test_conn_ok.png + img/trigger_columns.png + img/trigger.png + img/triggers.png + img/unique.png + img/view.png + img/views.png + img/virtual_table.png + img/warn_small.png + img/win_cascade.png + img/win_tile_horizontal.png + img/win_tile_vertical.png + img/win_tile.png + img/convert_db.png + img/window_rename.png + img/window_close.png + img/window_close_other.png + img/window_close_all.png + img/window_restore.png + img/integrity_check.png + img/vacuum_db.png + img/bug.png + img/feature_request.png + img/user_unknown.png + img/user.png + img/open_forum.png + img/user_manual.png + img/sqlite_docs.png + img/tip.png + img/licenses.png + img/homepage.png + img/bug_list.png + img/get_update.png + img/sqlitestudio.svg + img/sqlitestudio_16.png + img/keyboard.png + img/config_general.png + img/config_colors.png + img/config_font.png + img/config_look_and_feel.png + img/config_style.png + img/plugin.png + img/config_data_editors.png + img/database_export_wizard.svg + img/database_import_wizard.svg + img/database_offline.png + img/database_online.png + img/abort24.png + img/close.png + img/go_back.png + + diff --git a/SQLiteStudio3/guiSQLiteStudio/img/abort24.png b/SQLiteStudio3/guiSQLiteStudio/img/abort24.png new file mode 100644 index 0000000..87b6373 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/abort24.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png b/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png new file mode 100644 index 0000000..6b9fa6d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_abort.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png b/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png new file mode 100644 index 0000000..2c6152e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_clear.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png b/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png new file mode 100644 index 0000000..3836257 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_copy.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png b/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png new file mode 100644 index 0000000..85f80b5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_cut.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png b/SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png new file mode 100644 index 0000000..b9b8620 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_del_line.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png b/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png new file mode 100644 index 0000000..70b59dc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_delete.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png b/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png new file mode 100644 index 0000000..0cf8887 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_paste.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png b/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png new file mode 100644 index 0000000..56776f5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_redo.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_search.png b/SQLiteStudio3/guiSQLiteStudio/img/act_search.png new file mode 100644 index 0000000..7d57954 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_search.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png b/SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png new file mode 100644 index 0000000..775f1ec Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_select_all.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png b/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png new file mode 100644 index 0000000..aa4dd4e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/act_undo.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png new file mode 100644 index 0000000..1f69604 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png new file mode 100644 index 0000000..765cc19 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_re.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png new file mode 100644 index 0000000..9a8f226 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_sql.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png new file mode 100644 index 0000000..1747ca3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/apply_filter_txt.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/bug.png b/SQLiteStudio3/guiSQLiteStudio/img/bug.png new file mode 100644 index 0000000..242d539 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/bug.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png b/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png new file mode 100644 index 0000000..bbf004c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/bug_list.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/check.png b/SQLiteStudio3/guiSQLiteStudio/img/check.png new file mode 100644 index 0000000..0023b3a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/check.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png b/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png new file mode 100644 index 0000000..0cade7e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/clear_history.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png b/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png new file mode 100644 index 0000000..4671248 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/clear_lineedit.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/close.png b/SQLiteStudio3/guiSQLiteStudio/img/close.png new file mode 100644 index 0000000..6b9fa6d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/close.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/collation.png b/SQLiteStudio3/guiSQLiteStudio/img/collation.png new file mode 100644 index 0000000..94fa577 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/collation.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/column.png b/SQLiteStudio3/guiSQLiteStudio/img/column.png new file mode 100644 index 0000000..7d10ac9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/column.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png new file mode 100644 index 0000000..4270875 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/column_constraint.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/columns.png b/SQLiteStudio3/guiSQLiteStudio/img/columns.png new file mode 100644 index 0000000..4cbaa8a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/columns.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/commit.png b/SQLiteStudio3/guiSQLiteStudio/img/commit.png new file mode 100644 index 0000000..3b0e3fc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/commit.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/complete.png b/SQLiteStudio3/guiSQLiteStudio/img/complete.png new file mode 100644 index 0000000..358d9aa Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/complete.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png new file mode 100644 index 0000000..739efb5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_blob.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png new file mode 100644 index 0000000..440e626 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_no_value.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png new file mode 100644 index 0000000..e914d5c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_number.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png new file mode 100644 index 0000000..29bac66 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_operator.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png new file mode 100644 index 0000000..b723492 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_other.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png new file mode 100644 index 0000000..57de2d0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_pragma.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png b/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png new file mode 100644 index 0000000..2fc5ccc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/completer_string.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png b/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png new file mode 100644 index 0000000..df7ccc1 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_colors.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png b/SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png new file mode 100644 index 0000000..7fc1c7f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_data_editors.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_font.png b/SQLiteStudio3/guiSQLiteStudio/img/config_font.png new file mode 100644 index 0000000..b6911ca Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_font.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_general.png b/SQLiteStudio3/guiSQLiteStudio/img/config_general.png new file mode 100644 index 0000000..07f3522 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_general.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png b/SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png new file mode 100644 index 0000000..b697614 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_look_and_feel.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/config_style.png b/SQLiteStudio3/guiSQLiteStudio/img/config_style.png new file mode 100644 index 0000000..99a3802 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/config_style.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/configure.png b/SQLiteStudio3/guiSQLiteStudio/img/configure.png new file mode 100644 index 0000000..89ab15f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/configure.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png new file mode 100644 index 0000000..40a7bf9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/configure_constraint.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png b/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png new file mode 100644 index 0000000..15b5b1a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/convert_db.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database.png b/SQLiteStudio3/guiSQLiteStudio/img/database.png new file mode 100755 index 0000000..d588f42 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png b/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png new file mode 100644 index 0000000..7af930e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_connect.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png b/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png new file mode 100644 index 0000000..71bc905 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_connected.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png b/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png new file mode 100644 index 0000000..abc3282 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_disconnect.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_export.png b/SQLiteStudio3/guiSQLiteStudio/img/database_export.png new file mode 100644 index 0000000..417a4a2 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_export.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg b/SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg new file mode 100644 index 0000000..dc6b01f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/img/database_export_wizard.svg @@ -0,0 +1,284 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_file.png b/SQLiteStudio3/guiSQLiteStudio/img/database_file.png new file mode 100644 index 0000000..a36f74b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_file.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg b/SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg new file mode 100644 index 0000000..83ad166 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/img/database_import_wizard.svg @@ -0,0 +1,284 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png b/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png new file mode 100644 index 0000000..7ccd0a3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_invalid.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_network.png b/SQLiteStudio3/guiSQLiteStudio/img/database_network.png new file mode 100644 index 0000000..03f6f90 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_network.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png b/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png new file mode 100644 index 0000000..6888e5e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_offline.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_online.png b/SQLiteStudio3/guiSQLiteStudio/img/database_online.png new file mode 100644 index 0000000..d588f42 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_online.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png b/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png new file mode 100644 index 0000000..007b6d4 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/database_reload.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png b/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png new file mode 100644 index 0000000..57de2d0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/ddl_history.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/default.png b/SQLiteStudio3/guiSQLiteStudio/img/default.png new file mode 100644 index 0000000..f231987 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/default.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png new file mode 100644 index 0000000..6dc019a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/delete_row.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png new file mode 100644 index 0000000..ee473b9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/delete_selected.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png b/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png new file mode 100644 index 0000000..de13448 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/delete_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png b/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png new file mode 100644 index 0000000..c9fb206 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/denied_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory.png b/SQLiteStudio3/guiSQLiteStudio/img/directory.png new file mode 100644 index 0000000..260b415 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/directory.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png b/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png new file mode 100644 index 0000000..dbaa6ee Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/directory_open.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png b/SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png new file mode 100644 index 0000000..abcf5dc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/directory_open_with_db.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png b/SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png new file mode 100644 index 0000000..33c6d88 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/directory_with_db.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png b/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png new file mode 100644 index 0000000..8b941e3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/edit_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/erase.png b/SQLiteStudio3/guiSQLiteStudio/img/erase.png new file mode 100644 index 0000000..bc6a3fa Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/erase.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/error_small.png b/SQLiteStudio3/guiSQLiteStudio/img/error_small.png new file mode 100644 index 0000000..2a8f0f4 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/error_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png b/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png new file mode 100644 index 0000000..2dfaef5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/exec_query.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png b/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png new file mode 100644 index 0000000..298343e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/explain_query.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/export.png b/SQLiteStudio3/guiSQLiteStudio/img/export.png new file mode 100644 index 0000000..b8df9d6 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/export.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png b/SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png new file mode 100644 index 0000000..c619461 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/export_file_browse.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png b/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png new file mode 100644 index 0000000..49f5acc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/feature_request.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/fk.png b/SQLiteStudio3/guiSQLiteStudio/img/fk.png new file mode 100644 index 0000000..968bcb9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/fk.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png b/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png new file mode 100644 index 0000000..d5ab25d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/font_browse.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png b/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png new file mode 100644 index 0000000..6db18b4 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/format_sql.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/function.png b/SQLiteStudio3/guiSQLiteStudio/img/function.png new file mode 100644 index 0000000..18fa585 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/function.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/get_update.png b/SQLiteStudio3/guiSQLiteStudio/img/get_update.png new file mode 100644 index 0000000..bc7f13f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/get_update.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/go_back.png b/SQLiteStudio3/guiSQLiteStudio/img/go_back.png new file mode 100644 index 0000000..2a361a0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/go_back.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/help.png b/SQLiteStudio3/guiSQLiteStudio/img/help.png new file mode 100644 index 0000000..4ecaf37 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/help.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/homepage.png b/SQLiteStudio3/guiSQLiteStudio/img/homepage.png new file mode 100644 index 0000000..73377f9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/homepage.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/import.png b/SQLiteStudio3/guiSQLiteStudio/img/import.png new file mode 100644 index 0000000..4cab099 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/import.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/index.png b/SQLiteStudio3/guiSQLiteStudio/img/index.png new file mode 100644 index 0000000..7ac2093 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/index.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indexes.png b/SQLiteStudio3/guiSQLiteStudio/img/indexes.png new file mode 100644 index 0000000..90ddfab Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indexes.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png new file mode 100644 index 0000000..bb51f6f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indicator_error.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png new file mode 100644 index 0000000..0df7fb3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indicator_hint.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png new file mode 100644 index 0000000..d98378b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indicator_info.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png b/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png new file mode 100644 index 0000000..62e4690 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/indicator_warn.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png b/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png new file mode 100644 index 0000000..f14f2a3 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/info_balloon.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/info_small.png b/SQLiteStudio3/guiSQLiteStudio/img/info_small.png new file mode 100644 index 0000000..0e0db2a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/info_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png b/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png new file mode 100644 index 0000000..10d1f60 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/insert_row.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png b/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png new file mode 100644 index 0000000..55a2a7c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/insert_rows.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png b/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png new file mode 100644 index 0000000..57de2d0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/integrity_check.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png b/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png new file mode 100644 index 0000000..985b883 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/keyboard.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/keyword.png b/SQLiteStudio3/guiSQLiteStudio/img/keyword.png new file mode 100644 index 0000000..1ca13e8 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/keyword.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/licenses.png b/SQLiteStudio3/guiSQLiteStudio/img/licenses.png new file mode 100644 index 0000000..8918410 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/licenses.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/loading.gif b/SQLiteStudio3/guiSQLiteStudio/img/loading.gif new file mode 100644 index 0000000..5b33f7e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/loading.gif differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png b/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png new file mode 100644 index 0000000..46d56ae Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/minus_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/move_down.png b/SQLiteStudio3/guiSQLiteStudio/img/move_down.png new file mode 100644 index 0000000..9c53794 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/move_down.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/move_up.png b/SQLiteStudio3/guiSQLiteStudio/img/move_up.png new file mode 100644 index 0000000..a76885b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/move_up.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/not_null.png b/SQLiteStudio3/guiSQLiteStudio/img/not_null.png new file mode 100644 index 0000000..94af62e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/not_null.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png b/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png new file mode 100644 index 0000000..6aa2603 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/open_forum.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png new file mode 100644 index 0000000..e999199 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_editor.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png new file mode 100644 index 0000000..dbaa6ee Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/open_sql_file.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png b/SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png new file mode 100644 index 0000000..803ed0b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/open_value_editor.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_first.png b/SQLiteStudio3/guiSQLiteStudio/img/page_first.png new file mode 100644 index 0000000..37a6036 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/page_first.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_last.png b/SQLiteStudio3/guiSQLiteStudio/img/page_last.png new file mode 100644 index 0000000..16d38fc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/page_last.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_next.png b/SQLiteStudio3/guiSQLiteStudio/img/page_next.png new file mode 100644 index 0000000..5102071 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/page_next.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png b/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png new file mode 100644 index 0000000..058be1a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/page_prev.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/pk.png b/SQLiteStudio3/guiSQLiteStudio/img/pk.png new file mode 100644 index 0000000..8284636 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/pk.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/plugin.png b/SQLiteStudio3/guiSQLiteStudio/img/plugin.png new file mode 100644 index 0000000..172881f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/plugin.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png b/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png new file mode 100644 index 0000000..0f8a230 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/plus_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/question_small.png b/SQLiteStudio3/guiSQLiteStudio/img/question_small.png new file mode 100644 index 0000000..b6862db Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/question_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/reload.png b/SQLiteStudio3/guiSQLiteStudio/img/reload.png new file mode 100644 index 0000000..0a85ddc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/reload.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png b/SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png new file mode 100644 index 0000000..250d047 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/rename_fn_arg.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/results_below.png b/SQLiteStudio3/guiSQLiteStudio/img/results_below.png new file mode 100644 index 0000000..50e735d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/results_below.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png b/SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png new file mode 100644 index 0000000..00eb6fc Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/results_in_tab.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/rollback.png b/SQLiteStudio3/guiSQLiteStudio/img/rollback.png new file mode 100644 index 0000000..933272b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/rollback.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png b/SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png new file mode 100644 index 0000000..c619461 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/save_sql_file.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/set_null.png b/SQLiteStudio3/guiSQLiteStudio/img/set_null.png new file mode 100644 index 0000000..1c28f6c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/set_null.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png new file mode 100644 index 0000000..912f7d2 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_01.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png new file mode 100644 index 0000000..6800b00 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_02.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png new file mode 100644 index 0000000..a3991b0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_03.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png new file mode 100644 index 0000000..614958a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_04.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png new file mode 100644 index 0000000..fddfd80 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_05.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png new file mode 100644 index 0000000..599aa1c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_06.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png new file mode 100644 index 0000000..1fcbf6d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_07.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png new file mode 100644 index 0000000..e174c80 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_08.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png new file mode 100644 index 0000000..43e957b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_09.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png new file mode 100644 index 0000000..618c22e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_10.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png new file mode 100644 index 0000000..b498310 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_11.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png new file mode 100644 index 0000000..f17cf83 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_12.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png new file mode 100644 index 0000000..dece042 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_13.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png new file mode 100644 index 0000000..60fa094 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_14.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png new file mode 100644 index 0000000..dfb92a0 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_15.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png new file mode 100644 index 0000000..d93ff1e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_16.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png new file mode 100644 index 0000000..6755cd8 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_17.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png new file mode 100644 index 0000000..9418d92 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_18.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png new file mode 100644 index 0000000..0971931 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_19.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png new file mode 100644 index 0000000..0ccac28 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png new file mode 100644 index 0000000..31f56cd Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_cnt_20p.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png new file mode 100644 index 0000000..aa79bc1 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_columns.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png new file mode 100644 index 0000000..b8e6af1 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_asc.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png new file mode 100644 index 0000000..6d88b78 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_ind_desc.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png b/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png new file mode 100644 index 0000000..5717d70 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sort_reset.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sql.png b/SQLiteStudio3/guiSQLiteStudio/img/sql.png new file mode 100644 index 0000000..ea232a7 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sql.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png b/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png new file mode 100644 index 0000000..32df661 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sqlite_docs.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns new file mode 100644 index 0000000..70ac0cd Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.icns differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico new file mode 100644 index 0000000..0aef62d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.ico differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg new file mode 100644 index 0000000..af50fa0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio.svg @@ -0,0 +1,384 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png new file mode 100644 index 0000000..b319f7b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/sqlitestudio_16.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_error.png b/SQLiteStudio3/guiSQLiteStudio/img/status_error.png new file mode 100644 index 0000000..5177258 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/status_error.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_info.png b/SQLiteStudio3/guiSQLiteStudio/img/status_info.png new file mode 100644 index 0000000..fa9a60b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/status_info.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png b/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png new file mode 100644 index 0000000..567704b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/status_warn.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table.png b/SQLiteStudio3/guiSQLiteStudio/img/table.png new file mode 100644 index 0000000..b0cd69f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png b/SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png new file mode 100644 index 0000000..86649bf Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_column_add.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png b/SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png new file mode 100644 index 0000000..a3fa27c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_column_delete.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png b/SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png new file mode 100644 index 0000000..670a79e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_column_edit.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png b/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png new file mode 100644 index 0000000..710190d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_constraint.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png b/SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png new file mode 100644 index 0000000..79c8e80 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_create_similar.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_export.png b/SQLiteStudio3/guiSQLiteStudio/img/table_export.png new file mode 100644 index 0000000..e783a13 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_export.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_import.png b/SQLiteStudio3/guiSQLiteStudio/img/table_import.png new file mode 100644 index 0000000..d753ad4 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_import.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png b/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png new file mode 100644 index 0000000..0626d0a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/table_populate.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tables.png b/SQLiteStudio3/guiSQLiteStudio/img/tables.png new file mode 100644 index 0000000..4ac0456 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/tables.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png b/SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png new file mode 100644 index 0000000..864bd01 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/tabs_at_bottom.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png b/SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png new file mode 100644 index 0000000..d956ac2 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/tabs_on_top.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png new file mode 100644 index 0000000..0496522 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_error.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png new file mode 100644 index 0000000..b3f7858 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/test_conn_ok.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/tip.png b/SQLiteStudio3/guiSQLiteStudio/img/tip.png new file mode 100644 index 0000000..845e110 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/tip.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/trigger.png b/SQLiteStudio3/guiSQLiteStudio/img/trigger.png new file mode 100644 index 0000000..efc599d Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/trigger.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png b/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png new file mode 100644 index 0000000..2ad2087 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/trigger_columns.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/triggers.png b/SQLiteStudio3/guiSQLiteStudio/img/triggers.png new file mode 100644 index 0000000..284c03f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/triggers.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/unique.png b/SQLiteStudio3/guiSQLiteStudio/img/unique.png new file mode 100644 index 0000000..ed7ec0e Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/unique.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user.png b/SQLiteStudio3/guiSQLiteStudio/img/user.png new file mode 100644 index 0000000..2d44726 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/user.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png b/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png new file mode 100644 index 0000000..069fae7 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/user_manual.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png b/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png new file mode 100644 index 0000000..09f73c2 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/user_unknown.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png b/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png new file mode 100644 index 0000000..463222b Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/vacuum_db.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/view.png b/SQLiteStudio3/guiSQLiteStudio/img/view.png new file mode 100644 index 0000000..8b4b28f Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/view.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/views.png b/SQLiteStudio3/guiSQLiteStudio/img/views.png new file mode 100644 index 0000000..c64d4d9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/views.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png b/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png new file mode 100644 index 0000000..fe899d8 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/virtual_table.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png b/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png new file mode 100644 index 0000000..f108a40 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/warn_small.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png b/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png new file mode 100644 index 0000000..ab3e4b2 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/win_cascade.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png b/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png new file mode 100644 index 0000000..d12ce37 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/win_tile.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png new file mode 100644 index 0000000..3c53cac Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_horizontal.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png new file mode 100644 index 0000000..b16177a Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/win_tile_vertical.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_close.png b/SQLiteStudio3/guiSQLiteStudio/img/window_close.png new file mode 100644 index 0000000..923f0f5 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_close.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png b/SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png new file mode 100644 index 0000000..60d9802 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_close_all.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png b/SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png new file mode 100644 index 0000000..96722d9 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_close_other.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png b/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png new file mode 100644 index 0000000..1faecb8 Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_rename.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png b/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png new file mode 100644 index 0000000..80cfa3c Binary files /dev/null and b/SQLiteStudio3/guiSQLiteStudio/img/window_restore.png differ diff --git a/SQLiteStudio3/guiSQLiteStudio/license.txt b/SQLiteStudio3/guiSQLiteStudio/license.txt new file mode 100644 index 0000000..f166cc5 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/license.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp new file mode 100644 index 0000000..c53d95c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mainwindow.cpp @@ -0,0 +1,909 @@ +#include "mainwindow.h" +#include "common/unused.h" +#include "dbtree/dbtree.h" +#include "dbtree/dbtreemodel.h" +#include "iconmanager.h" +#include "windows/editorwindow.h" +#include "windows/tablewindow.h" +#include "windows/viewwindow.h" +#include "windows/functionseditor.h" +#include "windows/collationseditor.h" +#include "windows/ddlhistorywindow.h" +#include "mdiarea.h" +#include "statusfield.h" +#include "uiconfig.h" +#include "common/extaction.h" +#include "dbobjectdialogs.h" +#include "services/notifymanager.h" +#include "dialogs/configdialog.h" +#include "services/pluginmanager.h" +#include "formmanager.h" +#include "customconfigwidgetplugin.h" +#include "sqlitesyntaxhighlighter.h" +#include "qtscriptsyntaxhighlighter.h" +#include "services/exportmanager.h" +#include "services/importmanager.h" +#include "dialogs/exportdialog.h" +#include "dialogs/importdialog.h" +#include "multieditor/multieditorwidgetplugin.h" +#include "multieditor/multieditor.h" +#include "dialogs/dbdialog.h" +#include "uidebug.h" +#include "services/dbmanager.h" +#include "services/updatemanager.h" +#include "dialogs/aboutdialog.h" +#include "dialogs/bugdialog.h" +#include "windows/bugreporthistorywindow.h" +#include "dialogs/newversiondialog.h" +#include "dialogs/quitconfirmdialog.h" +#include "common/widgetcover.h" +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(MainWindow) +MainWindow* MainWindow::instance = nullptr; + +MainWindow::MainWindow() : + QMainWindow(), + ui(new Ui::MainWindow) +{ + init(); +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::init() +{ + ui->setupUi(this); + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanUp())); + +#ifdef Q_OS_WIN + setWindowIcon(ICONS.SQLITESTUDIO_APP.toQIcon().pixmap(256, 256)); +#else + setWindowIcon(ICONS.SQLITESTUDIO_APP); +#endif + + setWindowTitle(QString("SQLiteStudio (%1)").arg(SQLITESTUDIO->getVersionString())); + +#ifdef Q_OS_MACX + ui->centralWidget->layout()->setMargin(0); +#endif + + Committable::init(MainWindow::confirmQuit); + + DbTreeModel::staticInit(); + dbTree = new DbTree(this); + addDockWidget(Qt::LeftDockWidgetArea, dbTree); + + statusField = new StatusField(this); + addDockWidget(Qt::BottomDockWidgetArea, statusField); + if (!statusField->hasMessages()) + statusField->close(); + + initActions(); + + ui->mdiArea->setTaskBar(ui->taskBar); + addToolBar(Qt::BottomToolBarArea, ui->taskBar); + + addToolBar(Qt::TopToolBarArea, ui->viewToolbar); + insertToolBar(ui->viewToolbar, ui->mainToolBar); + insertToolBar(ui->mainToolBar, ui->structureToolbar); + insertToolBar(ui->structureToolbar, ui->dbToolbar); + + formManager = new FormManager(); + + initMenuBar(); + + PLUGINS->registerPluginType(tr("Configuration widgets")); + PLUGINS->registerPluginType(tr("Syntax highlighting engines")); + PLUGINS->registerPluginType(tr("Data editors")); + PLUGINS->loadBuiltInPlugin(new SqliteHighlighterPlugin); + PLUGINS->loadBuiltInPlugin(new JavaScriptHighlighterPlugin); + MultiEditor::loadBuiltInEditors(); + + updateWindowActions(); + + qApp->installEventFilter(this); + + if (isDebugEnabled()) + { + if (isDebugConsoleEnabled()) + notifyInfo(tr("Running in debug mode. Press %1 or use 'Help / Open debug console' menu entry to open the debug console.").arg(shortcuts[OPEN_DEBUG_CONSOLE]->get())); + else + notifyInfo(tr("Running in debug mode. Debug messages are printed to the standard output.")); + } + + connect(UPDATES, SIGNAL(updatesAvailable(QList)), this, SLOT(updatesAvailable(QList))); + connect(UPDATES, SIGNAL(noUpdatesAvailable()), this, SLOT(noUpdatesAvailable())); + connect(statusField, SIGNAL(linkActivated(QString)), this, SLOT(statusFieldLinkClicked(QString))); + + // Widget cover + widgetCover = new WidgetCover(this); + widgetCover->setVisible(false); + + updatingBusyBar = new QProgressBar(); + updatingBusyBar->setRange(0, 100); + updatingBusyBar->setTextVisible(true); + updatingBusyBar->setValue(0); + updatingBusyBar->setFixedWidth(300); + + updatingSubBar = new QProgressBar(); + updatingSubBar->setRange(0, 100); + updatingSubBar->setTextVisible(true); + updatingSubBar->setValue(0); + updatingSubBar->setFixedWidth(300); + + updatingLabel = new QLabel(); + + widgetCover->getContainerLayout()->addWidget(updatingLabel, 0, 0); + widgetCover->getContainerLayout()->addWidget(updatingBusyBar, 1, 0); + widgetCover->getContainerLayout()->addWidget(updatingSubBar, 2, 0); + connect(UPDATES, SIGNAL(updatingProgress(QString,int,int)), this, SLOT(handleUpdatingProgress(QString,int,int))); + connect(UPDATES, SIGNAL(updatingError(QString)), this, SLOT(handleUpdatingError())); +} + +void MainWindow::cleanUp() +{ + if (SQLITESTUDIO->getImmediateQuit()) + return; + + for (MdiWindow* win : getMdiArea()->getWindows()) + delete win; + + removeDockWidget(dbTree); + removeDockWidget(statusField); + + safe_delete(dbTree); + safe_delete(statusField); + + delete ui; + + safe_delete(formManager); +} + +EditorWindow* MainWindow::openSqlEditor() +{ + EditorWindow* win = new EditorWindow(ui->mdiArea); + if (win->isInvalid()) + { + delete win; + return nullptr; + } + + ui->mdiArea->addSubWindow(win); + return win; +} + +void MainWindow::updateWindowActions() +{ + bool hasActiveTask = ui->mdiArea->activeSubWindow(); + actionMap[MDI_CASCADE]->setEnabled(hasActiveTask); + actionMap[MDI_TILE]->setEnabled(hasActiveTask); + actionMap[MDI_TILE_HORIZONTAL]->setEnabled(hasActiveTask); + actionMap[MDI_TILE_VERTICAL]->setEnabled(hasActiveTask); + actionMap[CLOSE_WINDOW]->setEnabled(hasActiveTask); + actionMap[CLOSE_OTHER_WINDOWS]->setEnabled(hasActiveTask); + actionMap[CLOSE_ALL_WINDOWS]->setEnabled(hasActiveTask); + actionMap[RENAME_WINDOW]->setEnabled(hasActiveTask); + actionMap[RESTORE_WINDOW]->setEnabled(hasClosedWindowToRestore()); +} + +MdiArea *MainWindow::getMdiArea() const +{ + return dynamic_cast(ui->mdiArea); +} + +DbTree *MainWindow::getDbTree() const +{ + return dbTree; +} + +StatusField *MainWindow::getStatusField() const +{ + return statusField; +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + if (SQLITESTUDIO->getImmediateQuit()) + { + closingApp = true; + QMainWindow::closeEvent(event); + return; + } + + if (!Committable::canQuit()) + { + event->ignore(); + return; + } + + closingApp = true; + closeNonSessionWindows(); + MdiWindow* currWindow = ui->mdiArea->getCurrentWindow(); + hide(); + saveSession(currWindow); + QMainWindow::closeEvent(event); +} + +void MainWindow::createActions() +{ + createAction(OPEN_SQL_EDITOR, ICONS.OPEN_SQL_EDITOR, tr("Open SQL editor"), this, SLOT(openSqlEditorSlot()), ui->mainToolBar); + createAction(OPEN_DDL_HISTORY, ICONS.DDL_HISTORY, tr("Open DDL history"), this, SLOT(openDdlHistorySlot()), ui->mainToolBar); + createAction(OPEN_FUNCTION_EDITOR, ICONS.FUNCTION, tr("Open SQL functions editor"), this, SLOT(openFunctionEditorSlot()), ui->mainToolBar); + createAction(OPEN_COLLATION_EDITOR, ICONS.CONSTRAINT_COLLATION, tr("Open collations editor"), this, SLOT(openCollationEditorSlot()), ui->mainToolBar); + createAction(IMPORT, ICONS.IMPORT, tr("Import"), this, SLOT(importAnything()), ui->mainToolBar); + createAction(EXPORT, ICONS.EXPORT, tr("Export"), this, SLOT(exportAnything()), ui->mainToolBar); + ui->mainToolBar->addSeparator(); + createAction(OPEN_CONFIG, ICONS.CONFIGURE, tr("Open configuration dialog"), this, SLOT(openConfig()), ui->mainToolBar); + + createAction(MDI_TILE, ICONS.WIN_TILE, tr("Tile windows"), ui->mdiArea, SLOT(tileSubWindows()), ui->viewToolbar); + createAction(MDI_TILE_HORIZONTAL, ICONS.WIN_TILE_HORIZONTAL, tr("Tile windows horizontally"), ui->mdiArea, SLOT(tileHorizontally()), ui->viewToolbar); + createAction(MDI_TILE_VERTICAL, ICONS.WIN_TILE_VERTICAL, tr("Tile windows vertically"), ui->mdiArea, SLOT(tileVertically()), ui->viewToolbar); + createAction(MDI_CASCADE, ICONS.WIN_CASCADE, tr("Cascade windows"), ui->mdiArea, SLOT(cascadeSubWindows()), ui->viewToolbar); + createAction(NEXT_TASK, tr("Next window"), ui->taskBar, SLOT(nextTask()), this); + createAction(PREV_TASK, tr("Previous window"), ui->taskBar, SLOT(prevTask()), this); + createAction(HIDE_STATUS_FIELD, tr("Hide status field"), this, SLOT(hideStatusField()), this); + + createAction(CLOSE_WINDOW, ICONS.WIN_CLOSE, tr("Close selected window"), this, SLOT(closeSelectedWindow()), this); + createAction(CLOSE_OTHER_WINDOWS, ICONS.WIN_CLOSE_OTHER, tr("Close all windows but selected"), this, SLOT(closeAllWindowsButSelected()), this); + createAction(CLOSE_ALL_WINDOWS, ICONS.WIN_CLOSE_ALL, tr("Close all windows"), this, SLOT(closeAllWindows()), this); + createAction(RESTORE_WINDOW, ICONS.WIN_RESTORE, tr("Restore recently closed window"), this, SLOT(restoreLastClosedWindow()), this); + createAction(RENAME_WINDOW, ICONS.WIN_RENAME, tr("Rename selected window"), this, SLOT(renameWindow()), this); + + createAction(OPEN_DEBUG_CONSOLE, tr("Open Debug Console"), this, SLOT(openDebugConsole()), this); + createAction(REPORT_BUG, ICONS.BUG, tr("Report a bug"), this, SLOT(reportBug()), this); + createAction(FEATURE_REQUEST, ICONS.FEATURE_REQUEST, tr("Propose a new feature"), this, SLOT(requestFeature()), this); + createAction(ABOUT, ICONS.SQLITESTUDIO_APP16, tr("About"), this, SLOT(aboutSqlitestudio()), this); + createAction(LICENSES, ICONS.LICENSES, tr("Licenses"), this, SLOT(licenses()), this); + createAction(HOMEPAGE, ICONS.HOMEPAGE, tr("Open home page"), this, SLOT(homepage()), this); + createAction(FORUM, ICONS.OPEN_FORUM, tr("Open forum page"), this, SLOT(forum()), this); + createAction(USER_MANUAL, ICONS.USER_MANUAL, tr("User Manual"), this, SLOT(userManual()), this); + createAction(SQLITE_DOCS, ICONS.SQLITE_DOCS, tr("SQLite documentation"), this, SLOT(sqliteDocs()), this); + createAction(BUG_REPORT_HISTORY, ICONS.BUG_LIST, tr("Report history"), this, SLOT(reportHistory()), this); + createAction(CHECK_FOR_UPDATES, ICONS.GET_UPDATE, tr("Check for updates"), this, SLOT(checkForUpdates()), this); + + actionMap[ABOUT]->setMenuRole(QAction::AboutRole); + actionMap[OPEN_CONFIG]->setMenuRole(QAction::PreferencesRole); + + ui->dbToolbar->addAction(dbTree->getAction(DbTree::CONNECT_TO_DB)); + ui->dbToolbar->addAction(dbTree->getAction(DbTree::DISCONNECT_FROM_DB)); + ui->dbToolbar->addSeparator(); + ui->dbToolbar->addAction(dbTree->getAction(DbTree::ADD_DB)); + ui->dbToolbar->addAction(dbTree->getAction(DbTree::EDIT_DB)); + ui->dbToolbar->addAction(dbTree->getAction(DbTree::DELETE_DB)); + ui->dbToolbar->addSeparator(); + ui->dbToolbar->addAction(dbTree->getAction(DbTree::REFRESH_SCHEMA)); + + ui->structureToolbar->addAction(dbTree->getAction(DbTree::ADD_TABLE)); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::EDIT_TABLE)); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::DEL_TABLE)); + ui->structureToolbar->addSeparator(); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::ADD_INDEX)); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::EDIT_INDEX)); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::DEL_INDEX)); + ui->structureToolbar->addSeparator(); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::ADD_TRIGGER)); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::EDIT_TRIGGER)); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::DEL_TRIGGER)); + ui->structureToolbar->addSeparator(); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::ADD_VIEW)); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::EDIT_VIEW)); + ui->structureToolbar->addAction(dbTree->getAction(DbTree::DEL_VIEW)); + + ui->taskBar->initContextMenu(this); +} + +void MainWindow::initMenuBar() +{ + // Database menu + dbMenu = new QMenu(this); + dbMenu->setTitle(tr("Database", "menubar")); + menuBar()->addMenu(dbMenu); + + dbMenu->addAction(dbTree->getAction(DbTree::CONNECT_TO_DB)); + dbMenu->addAction(dbTree->getAction(DbTree::DISCONNECT_FROM_DB)); + dbMenu->addSeparator(); + dbMenu->addAction(dbTree->getAction(DbTree::ADD_DB)); + dbMenu->addAction(dbTree->getAction(DbTree::EDIT_DB)); + dbMenu->addAction(dbTree->getAction(DbTree::DELETE_DB)); + dbMenu->addSeparator(); + dbMenu->addAction(dbTree->getAction(DbTree::EXPORT_DB)); + dbMenu->addAction(dbTree->getAction(DbTree::CONVERT_DB)); + dbMenu->addAction(dbTree->getAction(DbTree::VACUUM_DB)); + dbMenu->addAction(dbTree->getAction(DbTree::INTEGRITY_CHECK)); + dbMenu->addSeparator(); + dbMenu->addAction(dbTree->getAction(DbTree::REFRESH_SCHEMA)); + dbMenu->addAction(dbTree->getAction(DbTree::REFRESH_SCHEMAS)); + + // Structure menu + structMenu = new QMenu(this); + structMenu->setTitle(tr("Structure", "menubar")); + menuBar()->addMenu(structMenu); + + structMenu->addAction(dbTree->getAction(DbTree::ADD_TABLE)); + structMenu->addAction(dbTree->getAction(DbTree::EDIT_TABLE)); + structMenu->addAction(dbTree->getAction(DbTree::DEL_TABLE)); + structMenu->addSeparator(); + structMenu->addAction(dbTree->getAction(DbTree::ADD_INDEX)); + structMenu->addAction(dbTree->getAction(DbTree::EDIT_INDEX)); + structMenu->addAction(dbTree->getAction(DbTree::DEL_INDEX)); + structMenu->addSeparator(); + structMenu->addAction(dbTree->getAction(DbTree::ADD_TRIGGER)); + structMenu->addAction(dbTree->getAction(DbTree::EDIT_TRIGGER)); + structMenu->addAction(dbTree->getAction(DbTree::DEL_TRIGGER)); + structMenu->addSeparator(); + structMenu->addAction(dbTree->getAction(DbTree::ADD_VIEW)); + structMenu->addAction(dbTree->getAction(DbTree::EDIT_VIEW)); + structMenu->addAction(dbTree->getAction(DbTree::DEL_VIEW)); + + // View menu + viewMenu = createPopupMenu(); + viewMenu->setTitle(tr("View", "menubar")); + menuBar()->addMenu(viewMenu); + + mdiMenu = new QMenu(viewMenu); + mdiMenu->setTitle(tr("Window list", "menubar view menu")); + connect(ui->mdiArea, &MdiArea::windowListChanged, this, &MainWindow::refreshMdiWindows); + + viewMenu->addSeparator(); + viewMenu->addAction(actionMap[MDI_TILE]); + viewMenu->addAction(actionMap[MDI_TILE_HORIZONTAL]); + viewMenu->addAction(actionMap[MDI_TILE_VERTICAL]); + viewMenu->addAction(actionMap[MDI_CASCADE]); + viewMenu->addSeparator(); + viewMenu->addAction(actionMap[CLOSE_WINDOW]); + viewMenu->addAction(actionMap[CLOSE_OTHER_WINDOWS]); + viewMenu->addAction(actionMap[CLOSE_ALL_WINDOWS]); + viewMenu->addSeparator(); + viewMenu->addAction(actionMap[RESTORE_WINDOW]); + viewMenu->addAction(actionMap[RENAME_WINDOW]); + + viewMenu->addSeparator(); + viewMenu->addMenu(mdiMenu); + + // Tools menu + toolsMenu = new QMenu(this); + toolsMenu->setTitle(tr("Tools", "menubar")); + menuBar()->addMenu(toolsMenu); + + toolsMenu->addAction(actionMap[OPEN_SQL_EDITOR]); + toolsMenu->addAction(actionMap[OPEN_DDL_HISTORY]); + toolsMenu->addAction(actionMap[OPEN_FUNCTION_EDITOR]); + toolsMenu->addAction(actionMap[OPEN_COLLATION_EDITOR]); + toolsMenu->addAction(actionMap[IMPORT]); + toolsMenu->addAction(actionMap[EXPORT]); + toolsMenu->addSeparator(); + toolsMenu->addAction(actionMap[OPEN_CONFIG]); + + // Help menu + sqlitestudioMenu = new QMenu(this); + sqlitestudioMenu->setTitle(tr("Help")); + menuBar()->addMenu(sqlitestudioMenu); + if (isDebugEnabled() && isDebugConsoleEnabled()) + { + sqlitestudioMenu->addAction(actionMap[OPEN_DEBUG_CONSOLE]); + sqlitestudioMenu->addSeparator(); + } + sqlitestudioMenu->addAction(actionMap[USER_MANUAL]); + sqlitestudioMenu->addAction(actionMap[SQLITE_DOCS]); + sqlitestudioMenu->addAction(actionMap[HOMEPAGE]); + sqlitestudioMenu->addAction(actionMap[FORUM]); + sqlitestudioMenu->addSeparator(); + if (UPDATES->isPlatformEligibleForUpdate()) + { + sqlitestudioMenu->addAction(actionMap[CHECK_FOR_UPDATES]); + sqlitestudioMenu->addSeparator(); + } + sqlitestudioMenu->addAction(actionMap[REPORT_BUG]); + sqlitestudioMenu->addAction(actionMap[FEATURE_REQUEST]); + sqlitestudioMenu->addAction(actionMap[BUG_REPORT_HISTORY]); + sqlitestudioMenu->addSeparator(); + sqlitestudioMenu->addAction(actionMap[LICENSES]); + sqlitestudioMenu->addAction(actionMap[ABOUT]); +} + +void MainWindow::saveSession(MdiWindow* currWindow) +{ + /* + * The currWindow is passed as parameter to the method to let hide the main window before + * saving session, because saving might take a while. + */ + QHash sessionValue; + sessionValue["state"] = saveState(); + sessionValue["geometry"] = saveGeometry(); + + QList windowSessions; + foreach (MdiWindow* window, ui->mdiArea->getWindows()) + if (window->restoreSessionNextTime()) + windowSessions << window->saveSession(); + + sessionValue["windowSessions"] = windowSessions; + + if (currWindow && currWindow->restoreSessionNextTime()) + { + QString title = currWindow->windowTitle(); + sessionValue["activeWindowTitle"] = title; + } + + sessionValue["dbTree"] = dbTree->saveSession(); + sessionValue["style"] = currentStyle(); + + CFG_UI.General.Session.set(sessionValue); +} + +void MainWindow::restoreSession() +{ + QHash sessionValue = CFG_UI.General.Session.get(); + if (sessionValue.size() == 0) + return; + + if (sessionValue.contains("style")) + setStyle(sessionValue["style"].toString()); + + if (sessionValue.contains("geometry")) + restoreGeometry(sessionValue["geometry"].toByteArray()); + + if (sessionValue.contains("state")) + restoreState(sessionValue["state"].toByteArray()); + + if (sessionValue.contains("dbTree")) + dbTree->restoreSession(sessionValue["dbTree"]); + + if (sessionValue.contains("windowSessions")) + restoreWindowSessions(sessionValue["windowSessions"].toList()); + + if (sessionValue.contains("activeWindowTitle")) + { + QString title = sessionValue["activeWindowTitle"].toString(); + MdiWindow* window = ui->mdiArea->getWindowByTitle(title); + if (window) + ui->mdiArea->setActiveSubWindow(window); + } + + if (statusField->hasMessages()) + statusField->setVisible(true); + + updateWindowActions(); +} + +void MainWindow::restoreWindowSessions(const QList& windowSessions) +{ + if (windowSessions.size() == 0) + return; + + foreach (const QVariant& winSession, windowSessions) + restoreWindowSession(winSession); +} + +MdiWindow* MainWindow::restoreWindowSession(const QVariant &windowSessions) +{ + QHash winSessionHash = windowSessions.toHash(); + if (!winSessionHash.contains("class")) + return nullptr; + + // Find out the type of stored session + QByteArray classBytes = winSessionHash["class"].toString().toLatin1(); + char* className = classBytes.data(); + int type = QMetaType::type(className); + if (type == QMetaType::UnknownType) + { + qWarning() << "Could not restore window session, because type" << className + << "is not known to Qt meta subsystem."; + return nullptr; + } + + // Try to instantiate the object + void* object = QMetaType::create(type); + if (!object) + { + qWarning() << "Could not restore window session, because type" << className + << "could not be instantiated."; + return nullptr; + } + + // Switch to session aware window, so we can use its session aware interface. + MdiChild* mdiChild = reinterpret_cast(object); + if (mdiChild->isInvalid()) + { + delete mdiChild; + return nullptr; + } + + // Add the window to MDI area and restore its session + MdiWindow* window = ui->mdiArea->addSubWindow(mdiChild); + if (!window->restoreSession(winSessionHash)) + delete window; + + return window; +} + +void MainWindow::setStyle(const QString& styleName) +{ + QStyle* style = QStyleFactory::create(styleName); + if (!style) + { + notifyWarn(tr("Could not set style: %1", "main window").arg(styleName)); + return; + } + QApplication::setStyle(style); +} + +QString MainWindow::currentStyle() const +{ + return QApplication::style()->objectName(); +} + +void MainWindow::closeNonSessionWindows() +{ + foreach (MdiWindow* window, ui->mdiArea->getWindows()) + if (!window->restoreSessionNextTime()) + window->close(); +} +FormManager* MainWindow::getFormManager() const +{ + return formManager; +} + +void MainWindow::setupDefShortcuts() +{ + BIND_SHORTCUTS(MainWindow, Action); +} + +void MainWindow::openSqlEditorSlot() +{ + openSqlEditor(); +} + +void MainWindow::refreshMdiWindows() +{ + mdiMenu->clear(); + + foreach (QAction* action, getMdiArea()->getTaskBar()->getTasks()) + mdiMenu->addAction(action); + + updateWindowActions(); +} + +void MainWindow::hideStatusField() +{ + statusField->close(); +} + +void MainWindow::openConfig() +{ + ConfigDialog config(this); + config.exec(); +} + +void MainWindow::openDdlHistorySlot() +{ + openDdlHistory(); +} + +void MainWindow::openFunctionEditorSlot() +{ + openFunctionEditor(); +} + +void MainWindow::openCollationEditorSlot() +{ + openCollationEditor(); +} + +void MainWindow::exportAnything() +{ + if (!ExportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot export, because no export plugin is loaded.")); + return; + } + + ExportDialog dialog(this); + dialog.exec(); +} + +void MainWindow::importAnything() +{ + if (!ImportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot import, because no import plugin is loaded.")); + return; + } + + ImportDialog dialog(this); + dialog.exec(); +} + +void MainWindow::closeAllWindows() +{ + ui->mdiArea->closeAllSubWindows(); +} + +void MainWindow::closeAllWindowsButSelected() +{ + ui->mdiArea->closeAllButActive(); +} + +void MainWindow::closeSelectedWindow() +{ + ui->mdiArea->closeActiveSubWindow(); +} + +void MainWindow::renameWindow() +{ + MdiWindow* win = ui->mdiArea->getActiveWindow(); + if (!win) + return; + + QString newTitle = QInputDialog::getText(this, tr("Rename window"), tr("Enter new name for the window:"), QLineEdit::Normal, win->windowTitle()); + if (newTitle == win->windowTitle() || newTitle.isEmpty()) + return; + + win->rename(newTitle); + +} + +void MainWindow::openDebugConsole() +{ + showUiDebugConsole(); +} + +void MainWindow::reportBug() +{ + BugDialog dialog(this); + dialog.exec(); +} + +void MainWindow::requestFeature() +{ + BugDialog dialog(this); + dialog.setFeatureRequestMode(true); + dialog.exec(); +} + +void MainWindow::aboutSqlitestudio() +{ + AboutDialog dialog(AboutDialog::ABOUT, this); + dialog.exec(); +} + +void MainWindow::licenses() +{ + AboutDialog dialog(AboutDialog::LICENSES, this); + dialog.exec(); +} + +void MainWindow::homepage() +{ + QDesktopServices::openUrl(QUrl(SQLITESTUDIO->getHomePage())); +} + +void MainWindow::forum() +{ + QDesktopServices::openUrl(QUrl(SQLITESTUDIO->getForumPage())); +} + +void MainWindow::userManual() +{ + QDesktopServices::openUrl(QUrl(SQLITESTUDIO->getUserManualPage())); +} + +void MainWindow::sqliteDocs() +{ + QDesktopServices::openUrl(QUrl(SQLITESTUDIO->getSqliteDocsPage())); +} + +void MainWindow::reportHistory() +{ + openReportHistory(); +} + +void MainWindow::updatesAvailable(const QList& updates) +{ + manualUpdatesChecking = false; + newVersionDialog = new NewVersionDialog(this); + newVersionDialog->setUpdates(updates); + notifyInfo(tr("New updates are available. Click here for details.").arg(openUpdatesUrl)); +} + +void MainWindow::noUpdatesAvailable() +{ + if (!manualUpdatesChecking) + return; + + notifyInfo(tr("You're running the most recent version. No updates are available.")); + manualUpdatesChecking = false; +} + +void MainWindow::statusFieldLinkClicked(const QString& link) +{ + if (link == openUpdatesUrl && newVersionDialog) + { + newVersionDialog->exec(); + return; + } +} + +void MainWindow::checkForUpdates() +{ + manualUpdatesChecking = true; + UPDATES->checkForUpdates(); +} + +void MainWindow::handleUpdatingProgress(const QString& jobTitle, int jobPercent, int totalPercent) +{ + if (!widgetCover->isVisible()) + widgetCover->show(); + + updatingLabel->setText(jobTitle); + updatingBusyBar->setValue(totalPercent); + updatingSubBar->setValue(jobPercent); +} + +void MainWindow::handleUpdatingError() +{ + widgetCover->hide(); +} + +DdlHistoryWindow* MainWindow::openDdlHistory() +{ + return openMdiWindow(); +} + +FunctionsEditor* MainWindow::openFunctionEditor() +{ + return openMdiWindow(); +} + +CollationsEditor* MainWindow::openCollationEditor() +{ + return openMdiWindow(); +} + +BugReportHistoryWindow* MainWindow::openReportHistory() +{ + return openMdiWindow(); +} + +bool MainWindow::confirmQuit(const QList& instances) +{ + QuitConfirmDialog dialog(MAINWINDOW); + + for (Committable* c : instances) + { + if (c->isUncommited()) + dialog.addMessage(c->getQuitUncommitedConfirmMessage()); + } + + if (dialog.getMessageCount() == 0) + return true; + + if (dialog.exec() == QDialog::Accepted) + return true; + + return false; +} + +bool MainWindow::isClosingApp() const +{ + return closingApp; +} + +QToolBar* MainWindow::getToolBar(int toolbar) const +{ + switch (static_cast(toolbar)) + { + case TOOLBAR_MAIN: + return ui->mainToolBar; + case TOOLBAR_DATABASE: + return ui->dbToolbar; + case TOOLBAR_STRUCTURE: + return ui->structureToolbar; + case TOOLBAR_VIEW: + return ui->viewToolbar; + } + return nullptr; +} + +void MainWindow::openDb(const QString& path) +{ + QString name = DBLIST->quickAddDb(path, QHash()); + if (!name.isNull()) + { + notifyInfo(tr("Database passed in command line parameters (%1) has been temporarily added to the list under name: %2").arg(path, name)); + Db* db = DBLIST->getByName(name); + db->open(); + } + else + notifyError(tr("Could not add database %1 to list.").arg(path)); +} + +QMenu* MainWindow::getDatabaseMenu() const +{ + return dbMenu; +} + +QMenu* MainWindow::getStructureMenu() const +{ + return structMenu; +} + +QMenu* MainWindow::getViewMenu() const +{ + return viewMenu; +} + +QMenu* MainWindow::getToolsMenu() const +{ + return toolsMenu; +} + +QMenu* MainWindow::getSQLiteStudioMenu() const +{ + return sqlitestudioMenu; +} + +MainWindow *MainWindow::getInstance() +{ + if (!instance) + instance = new MainWindow(); + + return instance; +} + +bool MainWindow::eventFilter(QObject* obj, QEvent* e) +{ + UNUSED(obj); + if (e->type() == QEvent::FileOpen) + { + QUrl url = dynamic_cast(e)->url(); + if (!url.isLocalFile()) + return false; + + DbDialog dialog(DbDialog::ADD, this); + dialog.setPath(url.toLocalFile()); + dialog.exec(); + return true; + } + return false; +} + +void MainWindow::pushClosedWindowSessionValue(const QVariant &value) +{ + closedWindowSessionValues.enqueue(value); + + if (closedWindowSessionValues.size() > closedWindowsStackSize) + closedWindowSessionValues.dequeue(); +} + +void MainWindow::restoreLastClosedWindow() +{ + if (closedWindowSessionValues.size() == 0) + return; + + QMdiSubWindow* activeWin = ui->mdiArea->activeSubWindow(); + bool maximizedMode = activeWin && activeWin->isMaximized(); + + QVariant winSession = closedWindowSessionValues.takeLast(); + if (maximizedMode) + { + QHash winSessionHash = winSession.toHash(); + winSessionHash.remove("geometry"); + winSession = winSessionHash; + } + + restoreWindowSession(winSession); +} + +bool MainWindow::hasClosedWindowToRestore() const +{ + return closedWindowSessionValues.size() > 0; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/mainwindow.h b/SQLiteStudio3/guiSQLiteStudio/mainwindow.h new file mode 100644 index 0000000..df12621 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mainwindow.h @@ -0,0 +1,234 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "common/extactioncontainer.h" +#include "db/db.h" +#include "ui_mainwindow.h" +#include "mdiwindow.h" +#include "services/updatemanager.h" +#include "guiSQLiteStudio_global.h" +#include +#include +#include + +class QUiLoader; +class DbTree; +class StatusField; +class EditorWindow; +class MdiArea; +class QActionGroup; +class MdiWindow; +class ViewWindow; +class TableWindow; +class FormManager; +class DdlHistoryWindow; +class FunctionsEditor; +class CollationsEditor; +class BugReportHistoryWindow; +class NewVersionDialog; +class Committable; +class WidgetCover; +class QProgressBar; +class QLabel; + +#ifdef Q_OS_MACX +#define PREV_TASK_KEY_SEQ Qt::CTRL + Qt::ALT + Qt::Key_Left +#define NEXT_TASK_KEY_SEQ Qt::CTRL + Qt::ALT + Qt::Key_Right +#else +#define PREV_TASK_KEY_SEQ Qt::CTRL + Qt::Key_PageUp +#define NEXT_TASK_KEY_SEQ Qt::CTRL + Qt::Key_PageDown +#endif + +CFG_KEY_LIST(MainWindow, QObject::tr("Main window"), + CFG_KEY_ENTRY(OPEN_SQL_EDITOR, Qt::ALT + Qt::Key_E, QObject::tr("Open SQL editor")) + CFG_KEY_ENTRY(PREV_TASK, PREV_TASK_KEY_SEQ, QObject::tr("Previous window")) + CFG_KEY_ENTRY(NEXT_TASK, NEXT_TASK_KEY_SEQ, QObject::tr("Next window")) + CFG_KEY_ENTRY(HIDE_STATUS_FIELD, Qt::Key_Escape, QObject::tr("Hide status area")) + CFG_KEY_ENTRY(OPEN_CONFIG, Qt::Key_F2, QObject::tr("Open configuration dialog")) + CFG_KEY_ENTRY(OPEN_DEBUG_CONSOLE, Qt::Key_F12, QObject::tr("Open Debug Console")) +) + +class GUI_API_EXPORT MainWindow : public QMainWindow, public ExtActionContainer +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + MDI_TILE, + MDI_CASCADE, + MDI_TILE_HORIZONTAL, + MDI_TILE_VERTICAL, + OPEN_SQL_EDITOR, + NEXT_TASK, + PREV_TASK, + HIDE_STATUS_FIELD, + OPEN_CONFIG, + OPEN_DDL_HISTORY, + OPEN_FUNCTION_EDITOR, + OPEN_COLLATION_EDITOR, + EXPORT, + IMPORT, + CLOSE_WINDOW, + CLOSE_ALL_WINDOWS, + CLOSE_OTHER_WINDOWS, + RESTORE_WINDOW, + RENAME_WINDOW, + OPEN_DEBUG_CONSOLE, + LICENSES, + HOMEPAGE, + FORUM, + USER_MANUAL, + SQLITE_DOCS, + REPORT_BUG, + FEATURE_REQUEST, + ABOUT, + BUG_REPORT_HISTORY, + CHECK_FOR_UPDATES + }; + + enum ToolBar + { + TOOLBAR_MAIN, + TOOLBAR_DATABASE, + TOOLBAR_STRUCTURE, + TOOLBAR_VIEW + }; + + static MainWindow* getInstance(); + + MdiArea* getMdiArea() const; + DbTree* getDbTree() const; + StatusField* getStatusField() const; + void restoreSession(); + void setStyle(const QString& styleName); + FormManager* getFormManager() const; + bool eventFilter(QObject* obj, QEvent* e); + void pushClosedWindowSessionValue(const QVariant& value); + bool hasClosedWindowToRestore() const; + bool isClosingApp() const; + QToolBar* getToolBar(int toolbar) const; + void openDb(const QString& path); + QMenu* getDatabaseMenu() const; + QMenu* getStructureMenu() const; + QMenu* getViewMenu() const; + QMenu* getToolsMenu() const; + QMenu* getSQLiteStudioMenu() const; + + protected: + void closeEvent(QCloseEvent *event); + + private: + MainWindow(); + ~MainWindow(); + + void init(); + void createActions(); + void setupDefShortcuts(); + void initMenuBar(); + void saveSession(MdiWindow* currWindow); + void restoreWindowSessions(const QList& windowSessions); + MdiWindow *restoreWindowSession(const QVariant& windowSessions); + QString currentStyle() const; + void closeNonSessionWindows(); + DdlHistoryWindow* openDdlHistory(); + FunctionsEditor* openFunctionEditor(); + CollationsEditor* openCollationEditor(); + BugReportHistoryWindow* openReportHistory(); + + template + T* openMdiWindow(); + + static bool confirmQuit(const QList& instances); + + static MainWindow* instance; + static constexpr int closedWindowsStackSize = 20; + static_char* openUpdatesUrl = "open_updates://"; + + Ui::MainWindow *ui = nullptr; + DbTree* dbTree = nullptr; + StatusField* statusField = nullptr; + QMenu* mdiMenu = nullptr; + FormManager* formManager = nullptr; + QQueue closedWindowSessionValues; + bool closingApp = false; + QMenu* dbMenu = nullptr; + QMenu* structMenu = nullptr; + QMenu* viewMenu = nullptr; + QMenu* toolsMenu = nullptr; + QMenu* sqlitestudioMenu = nullptr; + QPointer newVersionDialog; + WidgetCover* widgetCover = nullptr; + QLabel* updatingLabel = nullptr; + QProgressBar* updatingBusyBar = nullptr; + QProgressBar* updatingSubBar = nullptr; + bool manualUpdatesChecking = false; + + public slots: + EditorWindow* openSqlEditor(); + void updateWindowActions(); + + private slots: + void cleanUp(); + void openSqlEditorSlot(); + void refreshMdiWindows(); + void hideStatusField(); + void openConfig(); + void openDdlHistorySlot(); + void openFunctionEditorSlot(); + void openCollationEditorSlot(); + void exportAnything(); + void importAnything(); + void closeAllWindows(); + void closeAllWindowsButSelected(); + void closeSelectedWindow(); + void restoreLastClosedWindow(); + void renameWindow(); + void openDebugConsole(); + void reportBug(); + void requestFeature(); + void aboutSqlitestudio(); + void licenses(); + void homepage(); + void forum(); + void userManual(); + void sqliteDocs(); + void reportHistory(); + void updatesAvailable(const QList& updates); + void noUpdatesAvailable(); + void statusFieldLinkClicked(const QString& link); + void checkForUpdates(); + void handleUpdatingProgress(const QString& jobTitle, int jobPercent, int totalPercent); + void handleUpdatingError(); +}; + +template +T* MainWindow::openMdiWindow() +{ + T* win = nullptr; + foreach (MdiWindow* mdiWin, ui->mdiArea->getWindows()) + { + win = dynamic_cast(mdiWin->getMdiChild()); + if (win) + { + ui->mdiArea->setActiveSubWindow(mdiWin); + return win; + } + } + + win = new T(ui->mdiArea); + if (win->isInvalid()) + { + delete win; + return nullptr; + } + + ui->mdiArea->addSubWindow(win); + return win; +} + + +#define MAINWINDOW MainWindow::getInstance() + +#endif // MAINWINDOW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/mainwindow.ui b/SQLiteStudio3/guiSQLiteStudio/mainwindow.ui new file mode 100644 index 0000000..0ae72c8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mainwindow.ui @@ -0,0 +1,142 @@ + + + MainWindow + + + + 0 + 0 + 964 + 626 + + + + SQLiteStudio (?.?.?) + + + + + + true + + + QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks + + + true + + + + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + + + 138 + 138 + 138 + + + + + true + + + + + + + + + 0 + 0 + 964 + 22 + + + + + + Database toolbar + + + TopToolBarArea + + + false + + + + + Structure toolbar + + + TopToolBarArea + + + false + + + + + Tools + + + TopToolBarArea + + + false + + + + + Qt::CustomContextMenu + + + Window list + + + Qt::ToolButtonTextBesideIcon + + + TopToolBarArea + + + false + + + + + View toolbar + + + TopToolBarArea + + + false + + + + + + + MdiArea + QMdiArea +
    mdiarea.h
    + 1 +
    + + TaskBar + QToolBar +
    taskbar.h
    +
    +
    + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp b/SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp new file mode 100644 index 0000000..d53874c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mdiarea.cpp @@ -0,0 +1,264 @@ +#include "mdiarea.h" +#include "mainwindow.h" +#include "iconmanager.h" +#include "mdichild.h" +#include "mdiwindow.h" +#include "taskbar.h" +#include +#include +#include +#include + +MdiArea::MdiArea(QWidget *parent) : + QMdiArea(parent) +{ +} + +MdiWindow *MdiArea::addSubWindow(MdiChild *mdiChild) +{ + MdiWindow* mdiWin = new MdiWindow(mdiChild, this); + QMdiArea::addSubWindow(mdiWin); + mdiWin->show(); + + if (taskBar) + { + QAction* action = taskBar->addTask(mdiWin->windowIcon(), mdiWin->windowTitle()); + action->setCheckable(true); + action->setChecked(true); + actionToWinMap[action] = mdiWin; + winToActionMap[mdiWin] = action; + + connect(action, &QAction::triggered, this, &MdiArea::taskActivated); + connect(mdiWin, &QMdiSubWindow::aboutToActivate, this, &MdiArea::windowActivated); + } + + if (!mdiChild->handleInitialFocus()) + mdiChild->setFocus(); + + emit windowListChanged(); + return mdiWin; +} + +MdiWindow *MdiArea::getActiveWindow() +{ + return dynamic_cast(activeSubWindow()); +} + +void MdiArea::setTaskBar(TaskBar* value) +{ + taskBar = value; +} + +TaskBar* MdiArea::getTaskBar() const +{ + return taskBar; +} + +QAction* MdiArea::getTaskByWindow(MdiWindow* window) +{ + if (winToActionMap.contains(window)) + return winToActionMap[window]; + + return nullptr; +} + +QList MdiArea::getWindows() const +{ + QList windowList; + foreach(QAction* action, taskBar->getTasks()) + windowList << actionToWinMap[action]; + + return windowList; +} + +QList MdiArea::getMdiChilds() const +{ + QList childs; + for (MdiWindow* win : getWindows()) + childs << win->getMdiChild(); + + return childs; +} + +QList MdiArea::getWindowsToTile() const +{ + QList list; + foreach (MdiWindow *window, getWindows()) + { + if (window->isMinimized()) + continue; + + list << window; + } + return list; +} + +void MdiArea::taskActivated() +{ + QAction* action = dynamic_cast(sender()); + if (!action) + { + qWarning() << "MdiArea::taskActivated() slot called by sender that is not QAction."; + return; + } + + setActiveSubWindow(actionToWinMap[action]); +} + +void MdiArea::windowDestroyed(MdiWindow* window) +{ + if (!taskBar) + return; + + QAction* action = winToActionMap[window]; + winToActionMap.remove(window); + actionToWinMap.remove(action); + taskBar->removeTask(action); + delete action; + + emit windowListChanged(); +} + +void MdiArea::windowActivated() +{ + if (!taskBar) + return; + + MdiWindow* subWin = dynamic_cast(sender()); + if (!subWin) + { + qWarning() << "MdiArea::windowActivated() slot called by sender that is not QMdiSubWindow."; + return; + } + + QAction* action = winToActionMap[subWin]; + action->setChecked(true); +} + +void MdiArea::tileHorizontally() +{ + if (taskBar->isEmpty()) + return; + + bool gotFocus = false; + QPoint position(0, 0); + QList windowsToTile = getWindowsToTile(); + int winCnt = windowsToTile.count(); + foreach (MdiWindow *window, windowsToTile) + { + if (window->isMaximized()) + window->showNormal(); + + QRect rect(0, 0, width() / winCnt, height()); + window->setGeometry(rect); + window->move(position); + position.setX(position.x() + window->width()); + + if (window->hasFocus()) + gotFocus = true; + } + + if (!gotFocus && windowsToTile.size() > 0) + windowsToTile.first()->setFocus(); +} + +void MdiArea::tileVertically() +{ + if (taskBar->isEmpty()) + return; + + bool gotFocus = false; + QPoint position(0, 0); + QList windowsToTile = getWindowsToTile(); + int winCnt = windowsToTile.count(); + foreach (MdiWindow *window, windowsToTile) + { + if (window->isMaximized()) + window->showNormal(); + + QRect rect(0, 0, width(), height() / winCnt); + window->setGeometry(rect); + window->move(position); + position.setY(position.y() + window->height()); + + if (window->hasFocus()) + gotFocus = true; + } + + if (!gotFocus && windowsToTile.size() > 0) + windowsToTile.first()->setFocus(); +} + +void MdiArea::closeAllButActive() +{ + QList allButActive = subWindowList(); + allButActive.removeOne(activeSubWindow()); + + foreach (QMdiSubWindow *window, allButActive) + window->close(); +} + +MdiWindow* MdiArea::getWindowByChild(MdiChild *child) +{ + if (!child) + return nullptr; + + foreach (QMdiSubWindow *window, subWindowList()) + if (window->widget() == child) + return dynamic_cast(window); + + return nullptr; +} + +MdiWindow* MdiArea::getCurrentWindow() +{ + QMdiSubWindow* subWin = activeSubWindow(); + return dynamic_cast(subWin); +} + +bool MdiArea::isActiveSubWindow(MdiWindow *window) +{ + if (!window) + return false; + + QMdiSubWindow* subWin = currentSubWindow(); + if (!subWin) + return false; + + return window == subWin; +} + +bool MdiArea::isActiveSubWindow(MdiChild *child) +{ + if (!child) + return false; + + QMdiSubWindow* subWin = currentSubWindow(); + if (!subWin) + return false; + + return child == subWin->widget(); +} + +QStringList MdiArea::getWindowTitles() +{ + QStringList titles; + foreach (QMdiSubWindow *subWin, subWindowList()) + titles << subWin->windowTitle(); + + return titles; +} + +MdiWindow *MdiArea::getWindowByTitle(const QString &title) +{ + foreach (QMdiSubWindow *subWin, subWindowList()) + { + QString t = subWin->windowTitle(); + if (subWin->windowTitle() == title) + { + return dynamic_cast(subWin); + } + } + + return nullptr; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/mdiarea.h b/SQLiteStudio3/guiSQLiteStudio/mdiarea.h new file mode 100644 index 0000000..f34434e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mdiarea.h @@ -0,0 +1,73 @@ +#ifndef MDIAREA_H +#define MDIAREA_H + +#include "mdiwindow.h" +#include "guiSQLiteStudio_global.h" +#include + +class TaskBar; +class QActionGroup; +class MdiChild; + +class GUI_API_EXPORT MdiArea : public QMdiArea +{ + Q_OBJECT + public: + explicit MdiArea(QWidget *parent = 0); + + MdiWindow* addSubWindow(MdiChild* mdiChild); + MdiWindow* getActiveWindow(); + MdiWindow* getWindowByTitle(const QString& title); + MdiWindow* getWindowByChild(MdiChild* child); + MdiWindow* getCurrentWindow(); + bool isActiveSubWindow(MdiWindow* window); + bool isActiveSubWindow(MdiChild* child); + QStringList getWindowTitles(); + void setTaskBar(TaskBar *value); + TaskBar* getTaskBar() const; + QAction* getTaskByWindow(MdiWindow* window); + QList getWindows() const; + QList getMdiChilds() const; + + template + QList getMdiChilds() const; + + private: + QList getWindowsToTile() const; + + TaskBar* taskBar = nullptr; + QHash actionToWinMap; + QHash winToActionMap; + + signals: + void windowListChanged(); + + private slots: + void taskActivated(); + void windowActivated(); + + public slots: + void windowDestroyed(MdiWindow* window); + void tileHorizontally(); + void tileVertically(); + void closeAllButActive(); +}; + +template +QList MdiArea::getMdiChilds() const +{ + QList childs; + T* child = nullptr; + for (MdiWindow* win : getWindows()) + { + child = dynamic_cast(win->getMdiChild()); + if (child) + childs << child; + } + + return childs; +} + +#define MDIAREA MainWindow::getInstance()->getMdiArea() + +#endif // MDIAREA_H diff --git a/SQLiteStudio3/guiSQLiteStudio/mdichild.cpp b/SQLiteStudio3/guiSQLiteStudio/mdichild.cpp new file mode 100644 index 0000000..da45b42 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mdichild.cpp @@ -0,0 +1,77 @@ +#include "mdichild.h" +#include "mdiwindow.h" +#include "iconmanager.h" +#include "mainwindow.h" +#include + +MdiChild::MdiChild(QWidget* parent) : + QWidget(parent) +{ +} + +MdiChild::~MdiChild() +{ +} + +QVariant MdiChild::getSessionValue() +{ + QVariant value = saveSession(); + QHash hash = value.toHash(); + hash["class"] = QString(metaObject()->className()); + return hash; +} + +bool MdiChild::applySessionValue(const QVariant& sessionValue) +{ + bool result = restoreSession(sessionValue); + return result; +} + +MdiWindow* MdiChild::getMdiWindow() const +{ + return mdiWindow; +} + +void MdiChild::setMdiWindow(MdiWindow* value) +{ + mdiWindow = value; + if (mdiWindow) + { + mdiWindow->setWindowTitle(getTitleForMdiWindow()); + mdiWindow->setWindowIcon(*getIconNameForMdiWindow()); + } +} + +bool MdiChild::isInvalid() const +{ + return invalid; +} + +bool MdiChild::restoreSessionNextTime() +{ + return true; +} + +void MdiChild::updateWindowTitle() +{ + if (mdiWindow) + { + QString newTitle = getTitleForMdiWindow(); + if (mdiWindow->windowTitle() != newTitle) + mdiWindow->rename(newTitle); + } +} + +bool MdiChild::handleInitialFocus() +{ + return false; +} + +Db* MdiChild::getAssociatedDb() const +{ + return nullptr; +} + +void MdiChild::dbClosedFinalCleanup() +{ +} diff --git a/SQLiteStudio3/guiSQLiteStudio/mdichild.h b/SQLiteStudio3/guiSQLiteStudio/mdichild.h new file mode 100644 index 0000000..5ca20e5 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mdichild.h @@ -0,0 +1,47 @@ +#ifndef MDICHILD_H +#define MDICHILD_H + +#include "common/extactioncontainer.h" +#include "committable.h" +#include +#include + +class MdiWindow; +class Icon; +class Db; + +class GUI_API_EXPORT MdiChild : public QWidget, public ExtActionContainer, public Committable +{ + Q_OBJECT + public: + explicit MdiChild(QWidget* parent = 0); + ~MdiChild(); + + QVariant getSessionValue(); + bool applySessionValue(const QVariant& sessionValue); + + MdiWindow* getMdiWindow() const; + void setMdiWindow(MdiWindow* value); + bool isInvalid() const; + void updateWindowTitle(); + virtual bool restoreSessionNextTime(); + virtual bool handleInitialFocus(); + virtual Db* getAssociatedDb() const; + virtual void dbClosedFinalCleanup(); + + protected: + + virtual QVariant saveSession() = 0; + virtual bool restoreSession(const QVariant& sessionValue) = 0; + + virtual Icon* getIconNameForMdiWindow() = 0; + virtual QString getTitleForMdiWindow() = 0; + + bool invalid = false; + + private: + MdiWindow* mdiWindow = nullptr; +}; + + +#endif // MDICHILD_H diff --git a/SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp new file mode 100644 index 0000000..dafd108 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mdiwindow.cpp @@ -0,0 +1,201 @@ +#include "mdiwindow.h" +#include "mdichild.h" +#include "common/unused.h" +#include "mdiarea.h" +#include "mainwindow.h" +#include "services/dbmanager.h" +#include "db/db.h" +#include +#include +#include +#include +#include +#include +#include + +MdiWindow::MdiWindow(MdiChild* mdiChild, MdiArea *mdiArea, Qt::WindowFlags flags) : + QMdiSubWindow(mdiArea->viewport(), flags), mdiArea(mdiArea) +{ + setAttribute(Qt::WA_DeleteOnClose); + setWidget(mdiChild); + + connect(DBLIST, SIGNAL(dbAboutToBeDisconnected(Db*,bool&)), this, SLOT(dbAboutToBeDisconnected(Db*,bool&))); + connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*))); +} + +MdiWindow::~MdiWindow() +{ + if (SQLITESTUDIO->getImmediateQuit()) + return; + + if (!MAINWINDOW->isClosingApp()) + MAINWINDOW->pushClosedWindowSessionValue(saveSession()); + + mdiArea->windowDestroyed(this); +} + +QVariant MdiWindow::saveSession() +{ + if (!widget()) + return QVariant(); + + QHash hash = getMdiChild()->getSessionValue().toHash(); + hash["title"] = windowTitle(); + hash["position"] = pos(); + hash["geometry"] = saveGeometry(); + return hash; +} + +bool MdiWindow::restoreSession(const QVariant& sessionValue) +{ + if (!widget()) + return true; + + QHash value = sessionValue.toHash(); + if (value.size() == 0) + return true; + + if (value.contains("geometry")) + restoreGeometry(value["geometry"].toByteArray()); + + if (value.contains("position")) + move(value["position"].toPoint()); + + if (value.contains("title")) + { + QString title = value["title"].toString(); + rename(title); + } + + return getMdiChild()->applySessionValue(sessionValue); +} + +MdiChild* MdiWindow::getMdiChild() const +{ + if (!widget()) + return nullptr; + + return dynamic_cast(widget()); +} + +void MdiWindow::setWidget(MdiChild* value) +{ + QMdiSubWindow::setWidget(value); + if (value) + value->setMdiWindow(this); +} + +bool MdiWindow::restoreSessionNextTime() +{ + return getMdiChild()->restoreSessionNextTime(); +} + +void MdiWindow::rename(const QString& title) +{ + setWindowTitle(title); + + QAction* task = mdiArea->getTaskByWindow(this); + if (task) + task->setText(title); +} + +void MdiWindow::changeEvent(QEvent* event) +{ + if (event->type() != QEvent::WindowStateChange) + { + QMdiSubWindow::changeEvent(event); + return; + } + + QWindowStateChangeEvent *changeEvent = static_cast(event); + + bool wasActive = changeEvent->oldState().testFlag(Qt::WindowActive); + bool isActive = windowState().testFlag(Qt::WindowActive); + + /* + * This is a hack for a bug in Qt: QTBUG-23515. The problem is that when QMdiSubWindow + * changes its state (because we used shortcut, or the TaskBar to activate another window, + * or window is maximized or minimized), then the code responsible for hiding old window + * gives focus to its previous or next child widget (depending on what is the state change), + * before the new window is shown. This seems to be happening in QWidgetPrivate::hide_helper(). + * + */ + if (wasActive && isActive) + { + // Handle problem with maximize/minimize + QWidget* w = focusWidget(); + QMdiSubWindow::changeEvent(event); + if (w) + w->setFocus(); + } + else if (wasActive && !isActive) + { + // Handle problem with switching between 2 MDI windows - part 1 + lastFocusedWidget = focusWidget(); + QMdiSubWindow::changeEvent(event); + } + else if (!wasActive && isActive) + { + // Handle problem with switching between 2 MDI windows - part 2 + QMdiSubWindow::changeEvent(event); + if (!lastFocusedWidget.isNull() && (!focusWidget() || !isAncestorOf(focusWidget()))) + lastFocusedWidget->setFocus(); + } + else + QMdiSubWindow::changeEvent(event); +} + +void MdiWindow::closeEvent(QCloseEvent* e) +{ + if (dbBeingClosed || MAINWINDOW->isClosingApp() || !getMdiChild()->isUncommited()) + { + QMdiSubWindow::closeEvent(e); + return; + } + + if (confirmClose()) + QMdiSubWindow::closeEvent(e); + else + e->ignore(); +} + +void MdiWindow::dbAboutToBeDisconnected(Db* db, bool& deny) +{ + if (!db || getMdiChild()->getAssociatedDb() != db) + return; + + if (MAINWINDOW->isClosingApp()) + return; + + if (getMdiChild()->isUncommited() && !confirmClose()) + deny = true; + else + dbBeingClosed = true; +} + +void MdiWindow::dbDisconnected(Db* db) +{ + if (!db || getMdiChild()->getAssociatedDb() != db) + return; + + if (MAINWINDOW->isClosingApp()) + return; + + getMdiChild()->dbClosedFinalCleanup(); + close(); +} + +bool MdiWindow::confirmClose() +{ + QMessageBox msgBox(QMessageBox::Question, tr("Uncommited changes"), getMdiChild()->getQuitUncommitedConfirmMessage(), QMessageBox::Yes|QMessageBox::No, this); + msgBox.setDefaultButton(QMessageBox::No); + + QAbstractButton* closeBtn = msgBox.button(QMessageBox::Yes); + QAbstractButton* cancelBtn = msgBox.button(QMessageBox::No); + closeBtn->setText(tr("Close anyway")); + closeBtn->setIcon(ICONS.CLOSE); + cancelBtn->setText(tr("Don't close")); + cancelBtn->setIcon(ICONS.GO_BACK); + + return (msgBox.exec() == QMessageBox::Yes); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/mdiwindow.h b/SQLiteStudio3/guiSQLiteStudio/mdiwindow.h new file mode 100644 index 0000000..fd6c4dc --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/mdiwindow.h @@ -0,0 +1,43 @@ +#ifndef MDIWINDOW_H +#define MDIWINDOW_H + +#include "guiSQLiteStudio_global.h" +#include +#include + +class MdiChild; +class MdiArea; +class Db; + +class GUI_API_EXPORT MdiWindow : public QMdiSubWindow +{ + Q_OBJECT + + public: + MdiWindow(MdiChild* mdiChild, MdiArea *mdiArea, Qt::WindowFlags flags = 0); + virtual ~MdiWindow(); + + virtual QVariant saveSession(); + virtual bool restoreSession(const QVariant& sessionValue); + + MdiChild* getMdiChild() const; + void setWidget(MdiChild* value); + bool restoreSessionNextTime(); + void rename(const QString& title); + + void changeEvent(QEvent *event); + void closeEvent(QCloseEvent* e); + + private: + bool confirmClose(); + + QPointer lastFocusedWidget; + MdiArea* mdiArea = nullptr; + bool dbBeingClosed = false; + + private slots: + void dbAboutToBeDisconnected(Db* db, bool& deny); + void dbDisconnected(Db* db); +}; + +#endif // MDIWINDOW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp new file mode 100644 index 0000000..2bae2f9 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.cpp @@ -0,0 +1,366 @@ +#include "multieditor.h" +#include "multieditortext.h" +#include "multieditornumeric.h" +#include "multieditordatetime.h" +#include "multieditordate.h" +#include "multieditortime.h" +#include "multieditorbool.h" +#include "multieditorhex.h" +#include "mainwindow.h" +#include "common/unused.h" +#include "services/notifymanager.h" +#include "services/pluginmanager.h" +#include "multieditorwidgetplugin.h" +#include "uiconfig.h" +#include "dialogs/configdialog.h" +#include "formview.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QHash missingEditorPluginsAlreadyWarned; + +MultiEditor::MultiEditor(QWidget *parent) : + QWidget(parent) +{ + init(); +} + +void MultiEditor::init() +{ + QVBoxLayout* vbox = new QVBoxLayout(); + vbox->setMargin(margins); + vbox->setSpacing(spacing); + setLayout(vbox); + + QWidget* top = new QWidget(); + layout()->addWidget(top); + + QHBoxLayout* hbox = new QHBoxLayout(); + hbox->setMargin(0); + hbox->setSpacing(0); + top->setLayout(hbox); + + nullCheck = new QCheckBox(tr("Null value", "multieditor")); + hbox->addWidget(nullCheck); + + hbox->addStretch(); + + stateLabel = new QLabel(); + hbox->addWidget(stateLabel); + + hbox->addSpacing(50); + + tabs = new QTabWidget(); + layout()->addWidget(tabs); + tabs->tabBar()->installEventFilter(this); + + configBtn = new QToolButton(); + configBtn->setToolTip(tr("Configure editors for this data type")); + configBtn->setIcon(ICONS.CONFIGURE); + configBtn->setFocusPolicy(Qt::NoFocus); + configBtn->setAutoRaise(true); + configBtn->setEnabled(false); + connect(configBtn, SIGNAL(clicked()), this, SLOT(configClicked())); + tabs->setCornerWidget(configBtn); + + QGraphicsColorizeEffect* effect = new QGraphicsColorizeEffect(); + effect->setColor(Qt::black); + effect->setStrength(0.5); + nullEffect = effect; + tabs->setGraphicsEffect(effect); + + connect(tabs, &QTabWidget::currentChanged, this, &MultiEditor::tabChanged); + connect(nullCheck, &QCheckBox::stateChanged, this, &MultiEditor::nullStateChanged); + connect(this, SIGNAL(modified()), this, SLOT(setModified())); +} + +void MultiEditor::tabChanged(int idx) +{ + int prevTab = currentTab; + currentTab = idx; + + MultiEditorWidget* newEditor = editors[idx]; + newEditor->setFocus(); + + if (prevTab < 0) + return; + + if (newEditor->isUpToDate()) + return; + + MultiEditorWidget* prevEditor = editors[prevTab]; + newEditor->setValue(prevEditor->getValue()); + newEditor->setUpToDate(true); +} + +void MultiEditor::nullStateChanged(int state) +{ + bool checked = (state == Qt::Checked); + + if (checked) + valueBeforeNull = getValueOmmitNull(); + + updateNullEffect(); + updateValue(checked ? QVariant() : valueBeforeNull); + + if (!checked) + valueBeforeNull.clear(); + + tabs->setEnabled(!checked); + emit modified(); +} + +void MultiEditor::invalidateValue() +{ + if (invalidatingDisabled) + return; + + QObject* obj = sender(); + if (!obj) + { + qWarning() << "No sender object while invalidating MultiEditor value."; + return; + } + + QWidget* editorWidget = nullptr; + for (int i = 0; i < tabs->count(); i++) + { + editorWidget = tabs->widget(i); + if (editorWidget == obj) + continue; // skip sender + + dynamic_cast(editorWidget)->setUpToDate(false); + } + + emit modified(); +} + +void MultiEditor::setModified() +{ + valueModified = true; +} + +void MultiEditor::addEditor(MultiEditorWidget* editorWidget) +{ + editorWidget->setReadOnly(readOnly); + connect(editorWidget, SIGNAL(valueModified()), this, SLOT(invalidateValue())); + editors << editorWidget; + tabs->addTab(editorWidget, editorWidget->getTabLabel().replace("&", "&&")); + editorWidget->installEventFilter(this); +} + +void MultiEditor::showTab(int idx) +{ + tabs->setCurrentIndex(idx); +} + +void MultiEditor::setValue(const QVariant& value) +{ + nullCheck->setChecked(!value.isValid() || value.isNull()); + valueBeforeNull = value; + updateVisibility(); + updateValue(value); + valueModified = false; +} + +QVariant MultiEditor::getValue() const +{ + if (nullCheck->isChecked()) + return QVariant(); + + return getValueOmmitNull(); +} + +bool MultiEditor::isModified() const +{ + return valueModified; +} + +bool MultiEditor::eventFilter(QObject* obj, QEvent* event) +{ + if (event->type() == QEvent::Wheel) + { + QWidget::event(event); + return true; + } + + return QWidget::eventFilter(obj, event); +} + +bool MultiEditor::getReadOnly() const +{ + return readOnly; +} + +void MultiEditor::setReadOnly(bool value) +{ + readOnly = value; + + for (int i = 0; i < tabs->count(); i++) + dynamic_cast(tabs->widget(i))->setReadOnly(value); + + stateLabel->setVisible(readOnly); + nullCheck->setEnabled(!readOnly); + updateVisibility(); + updateLabel(); +} + +void MultiEditor::setDeletedRow(bool value) +{ + deleted = value; + updateLabel(); +} + +void MultiEditor::setDataType(const DataType& dataType) +{ + this->dataType = dataType; + + foreach (MultiEditorWidget* editorWidget, getEditorTypes(dataType)) + addEditor(editorWidget); + + showTab(0); + configBtn->setEnabled(true); +} + +void MultiEditor::focusThisEditor() +{ + MultiEditorWidget* w = dynamic_cast(tabs->currentWidget()); + if (!w) + return; + + w->focusThisWidget(); +} + +void MultiEditor::loadBuiltInEditors() +{ + PLUGINS->loadBuiltInPlugin(new MultiEditorBoolPlugin); + PLUGINS->loadBuiltInPlugin(new MultiEditorDateTimePlugin); + PLUGINS->loadBuiltInPlugin(new MultiEditorDatePlugin); + PLUGINS->loadBuiltInPlugin(new MultiEditorHexPlugin); + PLUGINS->loadBuiltInPlugin(new MultiEditorTextPlugin); + PLUGINS->loadBuiltInPlugin(new MultiEditorTimePlugin); + PLUGINS->loadBuiltInPlugin(new MultiEditorNumericPlugin); +} + +QList MultiEditor::getEditorTypes(const DataType& dataType) +{ + QList editors; + + QString typeStr = dataType.toString().trimmed().toUpper(); + QHash editorsOrder = CFG_UI.General.DataEditorsOrder.get(); + if (editorsOrder.contains(typeStr)) + { + MultiEditorWidgetPlugin* plugin = nullptr; + for (const QString& editorPluginName : editorsOrder[typeStr].toStringList()) + { + plugin = dynamic_cast(PLUGINS->getLoadedPlugin(editorPluginName)); + if (!plugin) + { + if (!missingEditorPluginsAlreadyWarned.contains(editorPluginName)) + { + notifyWarn(tr("Data editor plugin '%1' not loaded, while it is defined for editing '%1' data type.")); + missingEditorPluginsAlreadyWarned[editorPluginName] = true; + } + continue; + } + + editors << plugin->getInstance(); + } + } + + if (editors.size() > 0) + return editors; + + // + // Prepare default list of editors + // + QList plugins = PLUGINS->getLoadedPlugins(); + + typedef QPair EditorWithPriority; + + QList sortedEditors; + EditorWithPriority editorWithPrio; + for (MultiEditorWidgetPlugin* plugin : plugins) + { + if (!plugin->validFor(dataType)) + continue; + + editorWithPrio.first = plugin->getPriority(dataType); + editorWithPrio.second = plugin->getInstance(); + sortedEditors << editorWithPrio; + } + + qSort(sortedEditors.begin(), sortedEditors.end(), [=](const EditorWithPriority& ed1, const EditorWithPriority& ed2) -> bool + { + return ed1.first < ed2.first; + }); + + for (const EditorWithPriority& e : sortedEditors) + editors << e.second; + + return editors; +} + +void MultiEditor::configClicked() +{ + ConfigDialog config(MAINWINDOW); + config.configureDataEditors(dataType.toString()); + config.exec(); +} + +void MultiEditor::updateVisibility() +{ + tabs->setVisible(!readOnly || !nullCheck->isChecked()); + nullCheck->setVisible(!readOnly || nullCheck->isChecked()); + updateNullEffect(); +} + +void MultiEditor::updateNullEffect() +{ + nullEffect->setEnabled(tabs->isVisible() && nullCheck->isChecked()); + if (tabs->isVisible()) + { + for (int i = 0; i < tabs->count(); i++) + dynamic_cast(tabs->widget(i))->update(); + + nullEffect->update(); + } +} + +void MultiEditor::updateValue(const QVariant& newValue) +{ + invalidatingDisabled = true; + MultiEditorWidget* editorWidget = nullptr; + for (int i = 0; i < tabs->count(); i++) + { + editorWidget = dynamic_cast(tabs->widget(i)); + editorWidget->setValue(newValue); + editorWidget->setUpToDate(true); + } + invalidatingDisabled = false; +} + +void MultiEditor::updateLabel() +{ + if (deleted) + stateLabel->setText(""+tr("Deleted", "multieditor")+""); + else if (readOnly) + stateLabel->setText(""+tr("Read only", "multieditor")+""); + else + stateLabel->setText(""); +} + +QVariant MultiEditor::getValueOmmitNull() const +{ + return dynamic_cast(tabs->currentWidget())->getValue(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h new file mode 100644 index 0000000..2576c97 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditor.h @@ -0,0 +1,96 @@ +#ifndef MULTIEDITOR_H +#define MULTIEDITOR_H + +#include "guiSQLiteStudio_global.h" +#include "datagrid/sqlquerymodelcolumn.h" +#include +#include + +class QCheckBox; +class QTabWidget; +class MultiEditorWidget; +class QLabel; +class MultiEditorWidgetPlugin; +class QToolButton; + +class GUI_API_EXPORT MultiEditor : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QVariant value READ getValue WRITE setValue) + + public: + enum BuiltInEditor + { + TEXT, + NUMERIC, + BOOLEAN, + DATE, + TIME, + DATETIME, + HEX + }; + + explicit MultiEditor(QWidget *parent = 0); + + void addEditor(MultiEditorWidget* editorWidget); + void showTab(int idx); + + void setValue(const QVariant& value); + QVariant getValue() const; + bool isModified() const; + bool eventFilter(QObject* obj, QEvent* event); + bool getReadOnly() const; + void setReadOnly(bool value); + void setDeletedRow(bool value); + void setDataType(const DataType& dataType); + void focusThisEditor(); + + static void loadBuiltInEditors(); + + private: + void init(); + void updateVisibility(); + void updateNullEffect(); + void updateValue(const QVariant& newValue); + void updateLabel(); + QVariant getValueOmmitNull() const; + + static QList getEditorTypes(const DataType& dataType); + + static const int margins = 2; + static const int spacing = 2; + + QCheckBox* nullCheck = nullptr; + QTabWidget* tabs = nullptr; + QList editors; + QLabel* stateLabel = nullptr; + bool readOnly = false; + bool deleted = false; + bool invalidatingDisabled = false; + QGraphicsEffect* nullEffect = nullptr; + bool valueModified = false; + QVariant valueBeforeNull; + QToolButton* configBtn = nullptr; + DataType dataType; + + /** + * @brief currentTab + * Hold current tab index. It might seem as duplicate for tabs->currentIndex, + * but this is necessary when we want to know what was the previous tab, + * while being in tabChanged() slot. + */ + int currentTab = -1; + + private slots: + void configClicked(); + void tabChanged(int idx); + void nullStateChanged(int state); + void invalidateValue(); + void setModified(); + + signals: + void modified(); +}; + +#endif // MULTIEDITOR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp new file mode 100644 index 0000000..ed7c260 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.cpp @@ -0,0 +1,215 @@ +#include "multieditorbool.h" +#include +#include +#include + +QStringList MultiEditorBool::validValues; + +MultiEditorBool::MultiEditorBool(QWidget* parent) + : MultiEditorWidget(parent) +{ + setLayout(new QVBoxLayout()); + checkBox = new QCheckBox(); + layout()->addWidget(checkBox); + connect(checkBox, &QCheckBox::stateChanged, this, &MultiEditorBool::stateChanged); +} + +void MultiEditorBool::staticInit() +{ + validValues << "true" << "false" + << "yes" << "no" + << "on" << "off" + << "1" << "0"; +} + +void MultiEditorBool::setValue(const QVariant& value) +{ + switch (value.userType()) + { + case QVariant::Bool: + case QVariant::Int: + case QVariant::LongLong: + case QVariant::UInt: + case QVariant::ULongLong: + boolValue = value.toBool(); + upperCaseValue = false; + valueFormat = BOOL; + break; + default: + boolValue = valueFromString(value.toString()); + break; + } + + updateLabel(); + checkBox->setChecked(boolValue); +} + +bool MultiEditorBool::valueFromString(const QString& strValue) +{ + if (strValue.isEmpty()) + { + upperCaseValue = false; + valueFormat = BOOL; + return false; + } + + int idx = validValues.indexOf(strValue.toLower()); + if (idx < 0) + { + upperCaseValue = false; + valueFormat = BOOL; + return true; + } + + upperCaseValue = strValue[0].isUpper(); + switch (idx) + { + case 0: + case 1: + valueFormat = TRUE_FALSE; + break; + case 2: + case 3: + valueFormat = YES_NO; + break; + case 4: + case 5: + valueFormat = ON_OFF; + break; + case 6: + case 7: + valueFormat = ONE_ZERO; + break; + } + return !(bool)(idx % 2); +} + +QVariant MultiEditorBool::getValue() +{ + QString value; + switch (valueFormat) + { + case MultiEditorBool::TRUE_FALSE: + value = boolValue ? "true" : "false"; + break; + case MultiEditorBool::ON_OFF: + value = boolValue ? "on" : "off"; + break; + case MultiEditorBool::YES_NO: + value = boolValue ? "yes" : "no"; + break; + case MultiEditorBool::ONE_ZERO: + case MultiEditorBool::BOOL: + value = boolValue ? "1" : "0"; + break; + } + + if (value.isNull()) + value = boolValue ? "1" : "0"; + + if (upperCaseValue) + value = value.toUpper(); + + return value; +} + +void MultiEditorBool::setReadOnly(bool value) +{ + readOnly = value; +} + +QList MultiEditorBool::getNoScrollWidgets() +{ + QList list; + list << checkBox; + return list; +} + +QString MultiEditorBool::getTabLabel() +{ + return tr("Boolean"); +} + +void MultiEditorBool::focusThisWidget() +{ + checkBox->setFocus(); +} + +void MultiEditorBool::updateLabel() +{ + checkBox->setText(getValue().toString()); +} + +void MultiEditorBool::stateChanged(int state) +{ + if (readOnly && ((bool)state) != boolValue) + { + checkBox->setChecked(boolValue); + return; + } + + boolValue = checkBox->isChecked(); + updateLabel(); + emit valueModified(); +} + +MultiEditorWidget* MultiEditorBoolPlugin::getInstance() +{ + return new MultiEditorBool(); +} + +bool MultiEditorBoolPlugin::validFor(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BOOLEAN: + return true; + case DataType::BLOB: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::DATE: + case DataType::DATETIME: + case DataType::TIME: + case DataType::unknown: + break; + } + return false; +} + +int MultiEditorBoolPlugin::getPriority(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BOOLEAN: + return 1; + case DataType::BLOB: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::DATE: + case DataType::DATETIME: + case DataType::TIME: + case DataType::unknown: + break; + } + return 100; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h new file mode 100644 index 0000000..f328cf0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorbool.h @@ -0,0 +1,68 @@ +#ifndef MULTIEDITORBOOL_H +#define MULTIEDITORBOOL_H + +#include "guiSQLiteStudio_global.h" +#include "multieditorwidget.h" +#include "multieditorwidgetplugin.h" +#include "plugins/builtinplugin.h" +#include + +class QCheckBox; + +class GUI_API_EXPORT MultiEditorBool : public MultiEditorWidget +{ + Q_OBJECT + + public: + explicit MultiEditorBool(QWidget* parent = 0); + + static void staticInit(); + + void setValue(const QVariant& boolValue); + QVariant getValue(); + void setReadOnly(bool boolValue); + QList getNoScrollWidgets(); + QString getTabLabel(); + void focusThisWidget(); + + private: + enum Format + { + TRUE_FALSE, + ON_OFF, + YES_NO, + ONE_ZERO, + BOOL + }; + + bool valueFromString(const QString& strValue); + void updateLabel(); + + static QStringList validValues; + + QCheckBox* checkBox = nullptr; + Format valueFormat = ONE_ZERO; + bool upperCaseValue = false; + bool readOnly = false; + bool boolValue = false; + + private slots: + void stateChanged(int state); +}; + +class GUI_API_EXPORT MultiEditorBoolPlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + SQLITESTUDIO_PLUGIN_DESC("Boolean data editor.") + SQLITESTUDIO_PLUGIN_TITLE("Boolean") + SQLITESTUDIO_PLUGIN_VERSION(10000) + + public: + MultiEditorWidget* getInstance(); + bool validFor(const DataType& dataType); + int getPriority(const DataType& dataType); +}; + +#endif // MULTIEDITORBOOL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp new file mode 100644 index 0000000..44178f8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.cpp @@ -0,0 +1,87 @@ +#include "multieditordate.h" +#include + +QStringList MultiEditorDate::formats; + +MultiEditorDate::MultiEditorDate(QWidget* parent) + : MultiEditorDateTime(parent) +{ + setDisplayFormat(formats.first()); +} + +QString MultiEditorDate::getTabLabel() +{ + return tr("Date"); +} + +void MultiEditorDate::staticInit() +{ + formats << "yyyy-MM-dd"; +} + +QStringList MultiEditorDate::getParsingFormats() +{ + return MultiEditorDateTime::getParsingFormats(); +} + + +MultiEditorWidget*MultiEditorDatePlugin::getInstance() +{ + return new MultiEditorDate(); +} + +bool MultiEditorDatePlugin::validFor(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BLOB: + case DataType::BOOLEAN: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::DATETIME: + case DataType::TIME: + case DataType::unknown: + break; + case DataType::DATE: + return true; + } + return false; +} + +int MultiEditorDatePlugin::getPriority(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BLOB: + case DataType::BOOLEAN: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::TIME: + case DataType::DATETIME: + case DataType::unknown: + break; + case DataType::DATE: + return 1; + } + return 10; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h new file mode 100644 index 0000000..b6f6d7c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordate.h @@ -0,0 +1,37 @@ +#ifndef MULTIEDITORDATE_H +#define MULTIEDITORDATE_H + +#include "multieditordatetime.h" + +class GUI_API_EXPORT MultiEditorDate : public MultiEditorDateTime +{ + public: + explicit MultiEditorDate(QWidget *parent = 0); + + QString getTabLabel(); + + static void staticInit(); + + protected: + QStringList getParsingFormats(); + + private: + static QStringList formats; +}; + +class GUI_API_EXPORT MultiEditorDatePlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + SQLITESTUDIO_PLUGIN_DESC("Date data editor.") + SQLITESTUDIO_PLUGIN_TITLE("Date") + SQLITESTUDIO_PLUGIN_VERSION(10000) + + public: + MultiEditorWidget* getInstance(); + bool validFor(const DataType& dataType); + int getPriority(const DataType& dataType); +}; + +#endif // MULTIEDITORDATE_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp new file mode 100644 index 0000000..bd1e244 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.cpp @@ -0,0 +1,275 @@ +#include "multieditordatetime.h" +#include "common/utils.h" +#include "common/unused.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QStringList MultiEditorDateTime::formats; + +MultiEditorDateTime::MultiEditorDateTime(QWidget *parent) : + MultiEditorWidget(parent) +{ + QVBoxLayout* vbox = new QVBoxLayout(); + setLayout(vbox); + dateTimeEdit = new QDateTimeEdit(); + dateTimeLabel = new QLabel(); + calendar = new QCalendarWidget(); + // Extending width, becuase day labels are truncated on some systems. + calendar->setFixedSize(calendar->sizeHint() + QSize(80, 0)); + + vbox->addWidget(dateTimeEdit); + vbox->addWidget(dateTimeLabel); + vbox->addWidget(calendar); + + setDisplayFormat(formats.first()); + + connect(calendar, &QCalendarWidget::selectionChanged, this, &MultiEditorDateTime::calendarDateChanged); + connect(dateTimeEdit, &QDateTimeEdit::dateChanged, this, &MultiEditorDateTime::dateChanged); + connect(dateTimeEdit, &QDateTimeEdit::timeChanged, this, &MultiEditorDateTime::timeChanged); + + setFocusProxy(dateTimeEdit); + updateCalendarDisplay(); +} + +void MultiEditorDateTime::staticInit() +{ + formats << "yyyy-MM-dd hh:mm:ss" + << "yyyy-MM-dd hh:mm" + << "yyyy-MM-dd" + << "yyyy-MM-dd hh:mm:ss.z" + << "yyyy-MM-ddThh:mm" + << "yyyy-MM-ddThh:mm:ss" + << "yyyy-MM-ddThh:mm:ss.z"; +} + +void MultiEditorDateTime::setDisplayFormat(const QString& format) +{ + dateTimeEdit->setDisplayFormat(format); + dateTimeEdit->setMaximumWidth(dateTimeEdit->sizeHint().width()); +} + +void MultiEditorDateTime::setValue(const QVariant& value) +{ + switch (value.userType()) + { + case QVariant::DateTime: + dateTimeEdit->setDateTime(value.toDateTime()); + break; + case QVariant::Date: + dateTimeEdit->setDate(value.toDate()); + break; + default: + { + dateTimeEdit->setDateTime(fromString(value.toString())); + break; + } + } + updateReadOnlyDisplay(); +} + +QVariant MultiEditorDateTime::getValue() +{ + if (formatType == STRING) + return dateTimeEdit->dateTime().toString(originalValueFormat); + else if (formatType == UNIXTIME) + return dateTimeEdit->dateTime().toTime_t(); + else if (formatType == JULIAN_DAY) + return toJulian(dateTimeEdit->dateTime()); + else + return dateTimeEdit->dateTime().toString(dateTimeEdit->displayFormat()); +} + +QList MultiEditorDateTime::getNoScrollWidgets() +{ + QList list; + list << dateTimeEdit << calendar; + + QObject* obj = calendar->findChild("qt_calendar_calendarview"); + if (obj) + { + QTableView* view = dynamic_cast(obj); + if (view) + list << view->viewport(); + } + + return list; +} + +QDateTime MultiEditorDateTime::fromString(const QString& value) +{ + QDateTime dateTime; + foreach (const QString& format, getParsingFormats()) + { + dateTime = QDateTime::fromString(value, format); + if (dateTime.isValid()) + { + formatType = STRING; + originalValueFormat = format; + return dateTime; + } + } + + // Try with unixtime + bool ok; + uint unixtime = value.toUInt(&ok); + if (ok) + { + dateTime = QDateTime::fromTime_t(unixtime); + formatType = UNIXTIME; + return dateTime; + } + + // Try with Julian day + double jd = value.toDouble(&ok); + if (ok) + { + dateTime = toGregorian(jd); + formatType = JULIAN_DAY; + return dateTime; + } + + formatType = OTHER; + return QDateTime(); +} + +void MultiEditorDateTime::calendarDateChanged() +{ + if (updatingCalendar) + return; + + dateTimeEdit->setDate(calendar->selectedDate()); + emit valueModified(); +} + +void MultiEditorDateTime::dateChanged(const QDate& date) +{ + updatingCalendar = true; + calendar->setSelectedDate(date); + updatingCalendar = false; + emit valueModified(); +} + +void MultiEditorDateTime::timeChanged(const QTime& time) +{ + UNUSED(time); + emit valueModified(); +} + +bool MultiEditorDateTime::getReadOnly() const +{ + return readOnly; +} + +void MultiEditorDateTime::setReadOnly(bool value) +{ + readOnly = value; + dateTimeEdit->setVisible(!readOnly); + dateTimeLabel->setVisible(readOnly); + updateReadOnlyDisplay(); +} + +QString MultiEditorDateTime::getTabLabel() +{ + return tr("Date & time"); +} + +void MultiEditorDateTime::focusThisWidget() +{ + dateTimeEdit->setFocus(); +} + +QStringList MultiEditorDateTime::getParsingFormats() +{ + return formats; +} + +void MultiEditorDateTime::updateReadOnlyDisplay() +{ + if (!readOnly) + return; + + dateTimeLabel->setText(getValue().toString()); + QDate date = dateTimeEdit->date(); + calendar->setMinimumDate(date); + calendar->setMaximumDate(date); + calendar->setSelectedDate(date); +} + +void MultiEditorDateTime::updateCalendarDisplay() +{ + if (!showCalendars) + { + calendar->setVisible(false); + return; + } +} + +MultiEditorWidget*MultiEditorDateTimePlugin::getInstance() +{ + return new MultiEditorDateTime(); +} + +bool MultiEditorDateTimePlugin::validFor(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BLOB: + case DataType::BOOLEAN: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::TIME: + case DataType::unknown: + break; + case DataType::DATE: + case DataType::DATETIME: + return true; + } + return false; +} + +int MultiEditorDateTimePlugin::getPriority(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BLOB: + case DataType::BOOLEAN: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::TIME: + case DataType::unknown: + break; + case DataType::DATE: + return 2; + case DataType::DATETIME: + return 1; + } + return 10; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h new file mode 100644 index 0000000..59bd111 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordatetime.h @@ -0,0 +1,84 @@ +#ifndef MULTIEDITORDATETIME_H +#define MULTIEDITORDATETIME_H + +#include "multieditorwidget.h" +#include "multieditorwidgetplugin.h" +#include "plugins/builtinplugin.h" +#include +#include + +class QCalendarWidget; +class QDateTimeEdit; +class QLabel; + +class GUI_API_EXPORT MultiEditorDateTime : public MultiEditorWidget +{ + Q_OBJECT + public: + explicit MultiEditorDateTime(QWidget *parent = 0); + + static void staticInit(); + + void setValue(const QVariant& value); + QVariant getValue(); + bool needsValueUpdate(); + + QList getNoScrollWidgets(); + + bool getReadOnly() const; + void setReadOnly(bool value); + QString getTabLabel(); + void focusThisWidget(); + + protected: + void updateCalendarDisplay(); + void setDisplayFormat(const QString& format); + + virtual QStringList getParsingFormats(); + + QDateTimeEdit* dateTimeEdit = nullptr; + bool showCalendars = true; + + private: + enum FormatType + { + STRING, + JULIAN_DAY, + UNIXTIME, + OTHER + }; + + void updateReadOnlyDisplay(); + QDateTime fromString(const QString& value); + + static QStringList formats; + + QLabel* dateTimeLabel = nullptr; + QCalendarWidget* calendar = nullptr; + QString originalValueFormat; + FormatType formatType; + bool updatingCalendar = false; + bool readOnly = false; + + private slots: + void calendarDateChanged(); + void dateChanged(const QDate& date); + void timeChanged(const QTime& time); +}; + +class GUI_API_EXPORT MultiEditorDateTimePlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + SQLITESTUDIO_PLUGIN_DESC("Date and time data editor.") + SQLITESTUDIO_PLUGIN_TITLE("Date and time") + SQLITESTUDIO_PLUGIN_VERSION(10000) + + public: + MultiEditorWidget* getInstance(); + bool validFor(const DataType& dataType); + int getPriority(const DataType& dataType); +}; + +#endif // MULTIEDITORDATETIME_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp new file mode 100644 index 0000000..5e3985c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.cpp @@ -0,0 +1,49 @@ +#include "multieditordialog.h" +#include "multieditor.h" +#include +#include + +MultiEditorDialog::MultiEditorDialog(QWidget *parent) : + QDialog(parent) +{ + multiEditor = new MultiEditor(); + + QVBoxLayout* vbox = new QVBoxLayout(); + vbox->addWidget(multiEditor); + setLayout(vbox); + + multiEditor->setReadOnly(false); + + buttonBox = new QDialogButtonBox(Qt::Horizontal); + buttonBox->addButton(QDialogButtonBox::Ok); + buttonBox->addButton(QDialogButtonBox::Cancel); + vbox->addWidget(buttonBox); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +MultiEditorDialog::~MultiEditorDialog() +{ + delete multiEditor; +} + +void MultiEditorDialog::setValue(const QVariant& value) +{ + multiEditor->setValue(value); +} + +QVariant MultiEditorDialog::getValue() +{ + return multiEditor->getValue(); +} + +void MultiEditorDialog::setDataType(const DataType& dataType) +{ + multiEditor->setDataType(dataType); +} + +void MultiEditorDialog::setReadOnly(bool readOnly) +{ + multiEditor->setReadOnly(readOnly); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h new file mode 100644 index 0000000..ffbbd9c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditordialog.h @@ -0,0 +1,29 @@ +#ifndef MULTIEDITORDIALOG_H +#define MULTIEDITORDIALOG_H + +#include "datagrid/sqlquerymodelcolumn.h" +#include "guiSQLiteStudio_global.h" +#include + +class MultiEditor; +class QDialogButtonBox; + +class GUI_API_EXPORT MultiEditorDialog : public QDialog +{ + Q_OBJECT + public: + explicit MultiEditorDialog(QWidget *parent = 0); + ~MultiEditorDialog(); + + void setValue(const QVariant& value); + QVariant getValue(); + + void setDataType(const DataType& dataType); + void setReadOnly(bool readOnly); + + private: + MultiEditor* multiEditor = nullptr; + QDialogButtonBox* buttonBox = nullptr; +}; + +#endif // MULTIEDITORDIALOG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp new file mode 100644 index 0000000..5a3cd28 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.cpp @@ -0,0 +1,94 @@ +#include "multieditorhex.h" +#include "qhexedit2/qhexedit.h" +#include "common/unused.h" +#include + +MultiEditorHex::MultiEditorHex() +{ + setLayout(new QVBoxLayout()); + hexEdit = new QHexEdit(); + layout()->addWidget(hexEdit); + + //hexEdit->setTabChangesFocus(true); + + connect(hexEdit, SIGNAL(dataChanged()), this, SLOT(modificationChanged())); + setFocusProxy(hexEdit); +} + +MultiEditorHex::~MultiEditorHex() +{ +} + +void MultiEditorHex::setValue(const QVariant& value) +{ + hexEdit->setData(value.toByteArray()); +} + +QVariant MultiEditorHex::getValue() +{ + return hexEdit->data(); +} + +void MultiEditorHex::setReadOnly(bool value) +{ + hexEdit->setReadOnly(value); +} + +QString MultiEditorHex::getTabLabel() +{ + return tr("Hex"); +} + +void MultiEditorHex::focusThisWidget() +{ + hexEdit->setFocus(); +} + +QList MultiEditorHex::getNoScrollWidgets() +{ + return QList(); +} + +void MultiEditorHex::modificationChanged() +{ + emit valueModified(); +} + +MultiEditorWidget*MultiEditorHexPlugin::getInstance() +{ + return new MultiEditorHex(); +} + +bool MultiEditorHexPlugin::validFor(const DataType& dataType) +{ + UNUSED(dataType); + return true; +} + +int MultiEditorHexPlugin::getPriority(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BLOB: + return 1; + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::BOOLEAN: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::DATE: + case DataType::DATETIME: + case DataType::TIME: + case DataType::unknown: + break; + } + return 100; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h new file mode 100644 index 0000000..5fd32a0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorhex.h @@ -0,0 +1,50 @@ +#ifndef MULTIEDITORHEX_H +#define MULTIEDITORHEX_H + +#include "multieditorwidget.h" +#include "multieditorwidgetplugin.h" +#include "plugins/builtinplugin.h" +#include +#include + +class QHexEdit; +class QBuffer; + +class GUI_API_EXPORT MultiEditorHex : public MultiEditorWidget +{ + Q_OBJECT + public: + explicit MultiEditorHex(); + ~MultiEditorHex(); + + void setValue(const QVariant& value); + QVariant getValue(); + void setReadOnly(bool value); + QString getTabLabel(); + void focusThisWidget(); + + QList getNoScrollWidgets(); + + private: + QHexEdit* hexEdit = nullptr; + + private slots: + void modificationChanged(); +}; + +class GUI_API_EXPORT MultiEditorHexPlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + SQLITESTUDIO_PLUGIN_DESC("Hexadecimal data editor.") + SQLITESTUDIO_PLUGIN_TITLE("Hexadecimal") + SQLITESTUDIO_PLUGIN_VERSION(10000) + + public: + MultiEditorWidget* getInstance(); + bool validFor(const DataType& dataType); + int getPriority(const DataType& dataType); +}; + +#endif // MULTIEDITORHEX_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp new file mode 100644 index 0000000..198f71b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.cpp @@ -0,0 +1,109 @@ +#include "multieditornumeric.h" +#include "common/numericspinbox.h" +#include +#include + +MultiEditorNumeric::MultiEditorNumeric(QWidget* parent) + : MultiEditorWidget(parent) +{ + setLayout(new QVBoxLayout()); + spinBox = new NumericSpinBox(); + layout()->addWidget(spinBox); + + connect(spinBox, SIGNAL(modified()), this, SIGNAL(valueModified())); + + setFocusProxy(spinBox); +} + +void MultiEditorNumeric::setValue(const QVariant& value) +{ + spinBox->setValue(value); +} + +QVariant MultiEditorNumeric::getValue() +{ + return spinBox->getValue(); +} + +void MultiEditorNumeric::setReadOnly(bool value) +{ + spinBox->setReadOnly(value); +} + +QString MultiEditorNumeric::getTabLabel() +{ + return tr("Number"); +} + +void MultiEditorNumeric::focusThisWidget() +{ + spinBox->setFocus(); +} + +QList MultiEditorNumeric::getNoScrollWidgets() +{ + QList list; + list << spinBox; + return list; +} + +MultiEditorWidget*MultiEditorNumericPlugin::getInstance() +{ + return new MultiEditorNumeric(); +} + +bool MultiEditorNumericPlugin::validFor(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + return true; + case DataType::BOOLEAN: + case DataType::BLOB: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::DATE: + case DataType::DATETIME: + case DataType::TIME: + case DataType::unknown: + break; + } + return false; +} + +int MultiEditorNumericPlugin::getPriority(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + return 1; + case DataType::BOOLEAN: + case DataType::BLOB: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::DATE: + case DataType::DATETIME: + case DataType::TIME: + case DataType::unknown: + break; + } + return 10; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h new file mode 100644 index 0000000..65d0409 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditornumeric.h @@ -0,0 +1,42 @@ +#ifndef MULTIEDITORNUMERIC_H +#define MULTIEDITORNUMERIC_H + +#include "multieditorwidget.h" +#include "multieditorwidgetplugin.h" +#include "plugins/builtinplugin.h" + +class NumericSpinBox; + +class GUI_API_EXPORT MultiEditorNumeric : public MultiEditorWidget +{ + public: + explicit MultiEditorNumeric(QWidget *parent = 0); + + void setValue(const QVariant& value); + QVariant getValue(); + void setReadOnly(bool value); + QString getTabLabel(); + void focusThisWidget(); + + QList getNoScrollWidgets(); + + private: + NumericSpinBox* spinBox = nullptr; +}; + +class GUI_API_EXPORT MultiEditorNumericPlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + SQLITESTUDIO_PLUGIN_DESC("Numeric data editor.") + SQLITESTUDIO_PLUGIN_TITLE("Numeric types") + SQLITESTUDIO_PLUGIN_VERSION(10000) + + public: + MultiEditorWidget* getInstance(); + bool validFor(const DataType& dataType); + int getPriority(const DataType& dataType); +}; + +#endif // MULTIEDITORNUMERIC_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp new file mode 100644 index 0000000..05db8e0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.cpp @@ -0,0 +1,184 @@ +#include "multieditortext.h" +#include "common/unused.h" +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(MultiEditorText) + +MultiEditorText::MultiEditorText(QWidget *parent) : + MultiEditorWidget(parent) +{ + setLayout(new QVBoxLayout()); + textEdit = new QPlainTextEdit(); + layout()->addWidget(textEdit); + initActions(); + setupMenu(); + + setFocusProxy(textEdit); + textEdit->setContextMenuPolicy(Qt::CustomContextMenu); + textEdit->setTabChangesFocus(true); + + connect(textEdit, &QPlainTextEdit::modificationChanged, this, &MultiEditorText::modificationChanged); + connect(textEdit, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showCustomMenu(QPoint))); +} + +void MultiEditorText::setValue(const QVariant& value) +{ + textEdit->setPlainText(value.toString()); +} + +QVariant MultiEditorText::getValue() +{ + return textEdit->toPlainText(); +} + +void MultiEditorText::setReadOnly(bool value) +{ + textEdit->setReadOnly(value); +} + +QString MultiEditorText::getTabLabel() +{ + return tr("Text"); +} + +QToolBar* MultiEditorText::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return nullptr; +} + +void MultiEditorText::focusThisWidget() +{ + textEdit->setFocus(); +} + +QList MultiEditorText::getNoScrollWidgets() +{ + // We don't return text, we want it to be scrolled. + QList list; + return list; +} + +void MultiEditorText::modificationChanged(bool changed) +{ + if (changed) + emit valueModified(); +} + +void MultiEditorText::deleteSelected() +{ + textEdit->textCursor().removeSelectedText(); +} + +void MultiEditorText::showCustomMenu(const QPoint& point) +{ + contextMenu->popup(textEdit->mapToGlobal(point)); +} + +void MultiEditorText::updateUndoAction(bool enabled) +{ + actionMap[UNDO]->setEnabled(enabled); +} + +void MultiEditorText::updateRedoAction(bool enabled) +{ + actionMap[REDO]->setEnabled(enabled); +} + +void MultiEditorText::updateCopyAction(bool enabled) +{ + actionMap[CUT]->setEnabled(enabled); + actionMap[COPY]->setEnabled(enabled); + actionMap[DELETE]->setEnabled(enabled); +} + +void MultiEditorText::toggleTabFocus() +{ + textEdit->setTabChangesFocus(actionMap[TAB_CHANGES_FOCUS]->isChecked()); +} + +void MultiEditorText::createActions() +{ + createAction(TAB_CHANGES_FOCUS, tr("Tab changes focus"), this, SLOT(toggleTabFocus()), this); + createAction(CUT, ICONS.ACT_CUT, tr("Cut"), textEdit, SLOT(cut()), this); + createAction(COPY, ICONS.ACT_COPY, tr("Copy"), textEdit, SLOT(copy()), this); + createAction(PASTE, ICONS.ACT_PASTE, tr("Paste"), textEdit, SLOT(paste()), this); + createAction(DELETE, ICONS.ACT_DELETE, tr("Delete"), this, SLOT(deleteSelected()), this); + createAction(UNDO, ICONS.ACT_UNDO, tr("Undo"), textEdit, SLOT(undo()), this); + createAction(REDO, ICONS.ACT_REDO, tr("Redo"), textEdit, SLOT(redo()), this); + + actionMap[CUT]->setEnabled(false); + actionMap[COPY]->setEnabled(false); + actionMap[DELETE]->setEnabled(false); + actionMap[UNDO]->setEnabled(false); + actionMap[REDO]->setEnabled(false); + + actionMap[TAB_CHANGES_FOCUS]->setCheckable(true); + actionMap[TAB_CHANGES_FOCUS]->setChecked(true); + + connect(textEdit, &QPlainTextEdit::undoAvailable, this, &MultiEditorText::updateUndoAction); + connect(textEdit, &QPlainTextEdit::redoAvailable, this, &MultiEditorText::updateRedoAction); + connect(textEdit, &QPlainTextEdit::copyAvailable, this, &MultiEditorText::updateCopyAction); +} + +void MultiEditorText::setupDefShortcuts() +{ + BIND_SHORTCUTS(MultiEditorText, Action); +} + +void MultiEditorText::setupMenu() +{ + contextMenu = new QMenu(this); + contextMenu->addAction(actionMap[TAB_CHANGES_FOCUS]); + contextMenu->addSeparator(); + contextMenu->addAction(actionMap[UNDO]); + contextMenu->addAction(actionMap[REDO]); + contextMenu->addSeparator(); + contextMenu->addAction(actionMap[CUT]); + contextMenu->addAction(actionMap[COPY]); + contextMenu->addAction(actionMap[PASTE]); + contextMenu->addAction(actionMap[DELETE]); +} + +MultiEditorWidget* MultiEditorTextPlugin::getInstance() +{ + return new MultiEditorText(); +} + +bool MultiEditorTextPlugin::validFor(const DataType& dataType) +{ + UNUSED(dataType); + return true; +} + +int MultiEditorTextPlugin::getPriority(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BLOB: + case DataType::BOOLEAN: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::DATE: + case DataType::DATETIME: + case DataType::TIME: + return 10; + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::unknown: + break; + } + return 1; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h new file mode 100644 index 0000000..bd814ce --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortext.h @@ -0,0 +1,87 @@ +#ifndef MULTIEDITORTEXT_H +#define MULTIEDITORTEXT_H + +#include "multieditorwidget.h" +#include "multieditorwidgetplugin.h" +#include "common/extactioncontainer.h" +#include "plugins/builtinplugin.h" + +class QPlainTextEdit; +class QMenu; + +CFG_KEY_LIST(MultiEditorText, QObject::tr("Cell text value editor"), + CFG_KEY_ENTRY(CUT, QKeySequence::Cut, QObject::tr("Cut selected text")) + CFG_KEY_ENTRY(COPY, QKeySequence::Copy, QObject::tr("Copy selected text")) + CFG_KEY_ENTRY(PASTE, QKeySequence::Paste, QObject::tr("Paste from clipboard")) + CFG_KEY_ENTRY(DELETE, QKeySequence::Delete, QObject::tr("Delete selected text")) + CFG_KEY_ENTRY(UNDO, QKeySequence::Undo, QObject::tr("Undo")) + CFG_KEY_ENTRY(REDO, QKeySequence::Redo, QObject::tr("Redo")) +) + +class GUI_API_EXPORT MultiEditorText : public MultiEditorWidget, public ExtActionContainer +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + TAB_CHANGES_FOCUS, + CUT, + COPY, + PASTE, + DELETE, + UNDO, + REDO + }; + + enum ToolBar + { + }; + + explicit MultiEditorText(QWidget *parent = 0); + + void setValue(const QVariant& value); + QVariant getValue(); + void setReadOnly(bool value); + QString getTabLabel(); + QToolBar* getToolBar(int toolbar) const; + void focusThisWidget(); + QList getNoScrollWidgets(); + + protected: + void createActions(); + void setupDefShortcuts(); + + private: + void setupMenu(); + + QPlainTextEdit* textEdit = nullptr; + QMenu* contextMenu = nullptr; + + private slots: + void modificationChanged(bool changed); + void deleteSelected(); + void showCustomMenu(const QPoint& point); + void updateUndoAction(bool enabled); + void updateRedoAction(bool enabled); + void updateCopyAction(bool enabled); + void toggleTabFocus(); +}; + +class GUI_API_EXPORT MultiEditorTextPlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + SQLITESTUDIO_PLUGIN_DESC("Standard text data editor.") + SQLITESTUDIO_PLUGIN_TITLE("Text") + SQLITESTUDIO_PLUGIN_VERSION(10000) + + public: + MultiEditorWidget* getInstance(); + bool validFor(const DataType& dataType); + int getPriority(const DataType& dataType); +}; + +#endif // MULTIEDITORTEXT_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp new file mode 100644 index 0000000..8b49715 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.cpp @@ -0,0 +1,90 @@ +#include "multieditortime.h" +#include + +QStringList MultiEditorTime::formats; + +MultiEditorTime::MultiEditorTime(QWidget *parent) + : MultiEditorDateTime(parent) +{ + showCalendars = false; + updateCalendarDisplay(); + setDisplayFormat(formats.first()); +} + +QString MultiEditorTime::getTabLabel() +{ + return tr("Time"); +} + +void MultiEditorTime::staticInit() +{ + formats << "hh:mm:ss" + << "hh:mm:ss.zzz" + << "hh:mm"; +} + +QStringList MultiEditorTime::getParsingFormats() +{ + return formats; +} + +MultiEditorWidget*MultiEditorTimePlugin::getInstance() +{ + return new MultiEditorTime(); +} + +bool MultiEditorTimePlugin::validFor(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BLOB: + case DataType::BOOLEAN: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::DATE: + case DataType::DATETIME: + case DataType::unknown: + break; + case DataType::TIME: + return true; + } + return false; +} + +int MultiEditorTimePlugin::getPriority(const DataType& dataType) +{ + switch (dataType.getType()) + { + case DataType::BLOB: + case DataType::BOOLEAN: + case DataType::BIGINT: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::INTEGER: + case DataType::INT: + case DataType::NUMERIC: + case DataType::REAL: + case DataType::NONE: + case DataType::STRING: + case DataType::TEXT: + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::DATE: + case DataType::DATETIME: + case DataType::unknown: + break; + case DataType::TIME: + return 1; + } + return 10; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h new file mode 100644 index 0000000..56bf60e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditortime.h @@ -0,0 +1,38 @@ +#ifndef MULTIEDITORTIME_H +#define MULTIEDITORTIME_H + +#include "multieditordatetime.h" +#include "guiSQLiteStudio_global.h" + +class GUI_API_EXPORT MultiEditorTime : public MultiEditorDateTime +{ + public: + explicit MultiEditorTime(QWidget *parent = 0); + + QString getTabLabel(); + + static void staticInit(); + + protected: + QStringList getParsingFormats(); + + private: + static QStringList formats; +}; + +class GUI_API_EXPORT MultiEditorTimePlugin : public BuiltInPlugin, public MultiEditorWidgetPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + SQLITESTUDIO_PLUGIN_DESC("Time data editor.") + SQLITESTUDIO_PLUGIN_TITLE("Time") + SQLITESTUDIO_PLUGIN_VERSION(10000) + + public: + MultiEditorWidget* getInstance(); + bool validFor(const DataType& dataType); + int getPriority(const DataType& dataType); +}; + +#endif // MULTIEDITORTIME_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp new file mode 100644 index 0000000..caea9a5 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.cpp @@ -0,0 +1,23 @@ +#include "multieditorwidget.h" + +MultiEditorWidget::MultiEditorWidget(QWidget *parent) : + QWidget(parent) +{ +} + +void MultiEditorWidget::installEventFilter(QObject* filterObj) +{ + QObject::installEventFilter(filterObj); + foreach (QWidget* w, getNoScrollWidgets()) + w->installEventFilter(filterObj); +} + +bool MultiEditorWidget::isUpToDate() const +{ + return upToDate; +} + +void MultiEditorWidget::setUpToDate(bool value) +{ + upToDate = value; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h new file mode 100644 index 0000000..14bac26 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidget.h @@ -0,0 +1,33 @@ +#ifndef MULTIEDITORWIDGET_H +#define MULTIEDITORWIDGET_H + +#include "guiSQLiteStudio_global.h" +#include + +class GUI_API_EXPORT MultiEditorWidget : public QWidget +{ + Q_OBJECT + + public: + explicit MultiEditorWidget(QWidget *parent = 0); + + virtual void setValue(const QVariant& value) = 0; + virtual QVariant getValue() = 0; + virtual void setReadOnly(bool value) = 0; + virtual QList getNoScrollWidgets() = 0; + virtual QString getTabLabel() = 0; + virtual void focusThisWidget() = 0; + + void installEventFilter(QObject* filterObj); + + bool isUpToDate() const; + void setUpToDate(bool value); + + private: + bool upToDate = true; + + signals: + void valueModified(); +}; + +#endif // MULTIEDITORWIDGET_H diff --git a/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h new file mode 100644 index 0000000..011bde5 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/multieditor/multieditorwidgetplugin.h @@ -0,0 +1,17 @@ +#ifndef MULTIEDITORWIDGETPLUGIN_H +#define MULTIEDITORWIDGETPLUGIN_H + +#include "plugins/plugin.h" +#include "datagrid/sqlquerymodelcolumn.h" + +class MultiEditorWidget; + +class GUI_API_EXPORT MultiEditorWidgetPlugin : public virtual Plugin +{ + public: + virtual MultiEditorWidget* getInstance() = 0; + virtual bool validFor(const DataType& dataType) = 0; + virtual int getPriority(const DataType& dataType) = 0; +}; + +#endif // MULTIEDITORWIDGETPLUGIN_H diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp new file mode 100644 index 0000000..303091d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.cpp @@ -0,0 +1,115 @@ +#include "commands.h" + +CharCommand::CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, QUndoCommand *parent) + : QUndoCommand(parent) +{ + _xData = xData; + _charPos = charPos; + _newChar = newChar; + _cmd = cmd; +} + +bool CharCommand::mergeWith(const QUndoCommand *command) +{ + const CharCommand *nextCommand = static_cast(command); + bool result = false; + + if (_cmd != remove) + { + if (nextCommand->_cmd == replace) + if (nextCommand->_charPos == _charPos) + { + _newChar = nextCommand->_newChar; + result = true; + } + } + return result; +} + +void CharCommand::undo() +{ + switch (_cmd) + { + case insert: + _xData->remove(_charPos, 1); + break; + case replace: + _xData->replace(_charPos, _oldChar); + _xData->setDataChanged(_charPos, _wasChanged); + break; + case remove: + _xData->insert(_charPos, _oldChar); + _xData->setDataChanged(_charPos, _wasChanged); + break; + } +} + +void CharCommand::redo() +{ + switch (_cmd) + { + case insert: + _xData->insert(_charPos, _newChar); + break; + case replace: + _oldChar = _xData->data()[_charPos]; + _wasChanged = _xData->dataChanged(_charPos); + _xData->replace(_charPos, _newChar); + break; + case remove: + _oldChar = _xData->data()[_charPos]; + _wasChanged = _xData->dataChanged(_charPos); + _xData->remove(_charPos, 1); + break; + } +} + + + +ArrayCommand::ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa, int len, QUndoCommand *parent) + : QUndoCommand(parent) +{ + _cmd = cmd; + _xData = xData; + _baPos = baPos; + _newBa = newBa; + _len = len; +} + +void ArrayCommand::undo() +{ + switch (_cmd) + { + case insert: + _xData->remove(_baPos, _newBa.length()); + break; + case replace: + _xData->replace(_baPos, _oldBa); + _xData->setDataChanged(_baPos, _wasChanged); + break; + case remove: + _xData->insert(_baPos, _oldBa); + _xData->setDataChanged(_baPos, _wasChanged); + break; + } +} + +void ArrayCommand::redo() +{ + switch (_cmd) + { + case insert: + _xData->insert(_baPos, _newBa); + break; + case replace: + _oldBa = _xData->data().mid(_baPos, _len); + _wasChanged = _xData->dataChanged(_baPos, _len); + _xData->replace(_baPos, _newBa); + break; + case remove: + _oldBa = _xData->data().mid(_baPos, _len); + _wasChanged = _xData->dataChanged(_baPos, _len); + _xData->remove(_baPos, _len); + break; + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h new file mode 100644 index 0000000..b7e4921 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/commands.h @@ -0,0 +1,70 @@ +#ifndef COMMANDS_H +#define COMMANDS_H + +/** \cond docNever */ + +#include "guiSQLiteStudio_global.h" +#include "xbytearray.h" +#include + +/*! CharCommand is a class to prived undo/redo functionality in QHexEdit. +A QUndoCommand represents a single editing action on a document. CharCommand +is responsable for manipulations on single chars. It can insert. replace and +remove characters. A manipulation stores allways to actions +1. redo (or do) action +2. undo action. + +CharCommand also supports command compression via mergeWidht(). This allows +the user to execute a undo command contation e.g. 3 steps in a single command. +If you for example insert a new byt "34" this means for the editor doing 3 +steps: insert a "00", replace it with "03" and the replace it with "34". These +3 steps are combined into a single step, insert a "34". +*/ +class GUI_API_EXPORT CharCommand : public QUndoCommand +{ +public: + enum { Id = 1234 }; + enum Cmd {insert, remove, replace}; + + CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, + QUndoCommand *parent=0); + + void undo(); + void redo(); + bool mergeWith(const QUndoCommand *command); + int id() const { return Id; } + +private: + XByteArray * _xData; + int _charPos; + bool _wasChanged; + char _newChar; + char _oldChar; + Cmd _cmd; +}; + +/*! ArrayCommand provides undo/redo functionality for handling binary strings. It +can undo/redo insert, replace and remove binary strins (QByteArrays). +*/ +class GUI_API_EXPORT ArrayCommand : public QUndoCommand +{ +public: + enum Cmd {insert, remove, replace}; + ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa=QByteArray(), int len=0, + QUndoCommand *parent=0); + void undo(); + void redo(); + +private: + Cmd _cmd; + XByteArray * _xData; + int _baPos; + int _len; + QByteArray _wasChanged; + QByteArray _newBa; + QByteArray _oldBa; +}; + +/** \endcond docNever */ + +#endif // COMMANDS_H diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp new file mode 100644 index 0000000..b12624e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.cpp @@ -0,0 +1,180 @@ +#include + +#include "qhexedit.h" + + +QHexEdit::QHexEdit(QWidget *parent) : QScrollArea(parent) +{ + qHexEdit_p = new QHexEditPrivate(this); + setWidget(qHexEdit_p); + setWidgetResizable(true); + + connect(qHexEdit_p, SIGNAL(currentAddressChanged(int)), this, SIGNAL(currentAddressChanged(int))); + connect(qHexEdit_p, SIGNAL(currentSizeChanged(int)), this, SIGNAL(currentSizeChanged(int))); + connect(qHexEdit_p, SIGNAL(dataChanged()), this, SIGNAL(dataChanged())); + connect(qHexEdit_p, SIGNAL(overwriteModeChanged(bool)), this, SIGNAL(overwriteModeChanged(bool))); + setFocusPolicy(Qt::NoFocus); +} + +int QHexEdit::indexOf(const QByteArray & ba, int from) const +{ + return qHexEdit_p->indexOf(ba, from); +} + +void QHexEdit::insert(int i, const QByteArray & ba) +{ + qHexEdit_p->insert(i, ba); +} + +void QHexEdit::insert(int i, char ch) +{ + qHexEdit_p->insert(i, ch); +} + +int QHexEdit::lastIndexOf(const QByteArray & ba, int from) const +{ + return qHexEdit_p->lastIndexOf(ba, from); +} + +void QHexEdit::remove(int pos, int len) +{ + qHexEdit_p->remove(pos, len); +} + +void QHexEdit::replace( int pos, int len, const QByteArray & after) +{ + qHexEdit_p->replace(pos, len, after); +} + +QString QHexEdit::toReadableString() +{ + return qHexEdit_p->toRedableString(); +} + +QString QHexEdit::selectionToReadableString() +{ + return qHexEdit_p->selectionToReadableString(); +} + +void QHexEdit::setAddressArea(bool addressArea) +{ + qHexEdit_p->setAddressArea(addressArea); +} + +void QHexEdit::redo() +{ + qHexEdit_p->redo(); +} + +void QHexEdit::undo() +{ + qHexEdit_p->undo(); +} + +void QHexEdit::setAddressWidth(int addressWidth) +{ + qHexEdit_p->setAddressWidth(addressWidth); +} + +void QHexEdit::setAsciiArea(bool asciiArea) +{ + qHexEdit_p->setAsciiArea(asciiArea); +} + +void QHexEdit::setHighlighting(bool mode) +{ + qHexEdit_p->setHighlighting(mode); +} + +void QHexEdit::setAddressOffset(int offset) +{ + qHexEdit_p->setAddressOffset(offset); +} + +int QHexEdit::addressOffset() +{ + return qHexEdit_p->addressOffset(); +} + +void QHexEdit::setCursorPosition(int cursorPos) +{ + // cursorPos in QHexEditPrivate is the position of the textcoursor without + // blanks, means bytePos*2 + qHexEdit_p->setCursorPos(cursorPos*2); +} + +int QHexEdit::cursorPosition() +{ + return qHexEdit_p->cursorPos() / 2; +} + + +void QHexEdit::setData(const QByteArray &data) +{ + qHexEdit_p->setData(data); +} + +QByteArray QHexEdit::data() +{ + return qHexEdit_p->data(); +} + +void QHexEdit::setAddressAreaColor(const QColor &color) +{ + qHexEdit_p->setAddressAreaColor(color); +} + +QColor QHexEdit::addressAreaColor() +{ + return qHexEdit_p->addressAreaColor(); +} + +void QHexEdit::setHighlightingColor(const QColor &color) +{ + qHexEdit_p->setHighlightingColor(color); +} + +QColor QHexEdit::highlightingColor() +{ + return qHexEdit_p->highlightingColor(); +} + +void QHexEdit::setSelectionColor(const QColor &color) +{ + qHexEdit_p->setSelectionColor(color); +} + +QColor QHexEdit::selectionColor() +{ + return qHexEdit_p->selectionColor(); +} + +void QHexEdit::setOverwriteMode(bool overwriteMode) +{ + qHexEdit_p->setOverwriteMode(overwriteMode); +} + +bool QHexEdit::overwriteMode() +{ + return qHexEdit_p->overwriteMode(); +} + +void QHexEdit::setReadOnly(bool readOnly) +{ + qHexEdit_p->setReadOnly(readOnly); +} + +bool QHexEdit::isReadOnly() +{ + return qHexEdit_p->isReadOnly(); +} + +void QHexEdit::setFont(const QFont &font) +{ + qHexEdit_p->setFont(font); +} + +const QFont & QHexEdit::font() const +{ + return qHexEdit_p->font(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h new file mode 100644 index 0000000..b2d707c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit.h @@ -0,0 +1,230 @@ +#ifndef QHEXEDIT_H +#define QHEXEDIT_H + +#include "guiSQLiteStudio_global.h" +#include +#include +#include "qhexedit_p.h" + +/*! QHexEdit is a hex editor widget written in C++ for the Qt (Qt4) framework. +It is a simple editor for binary data, just like QPlainTextEdit is for text +data. There are sip configuration files included, so it is easy to create +bindings for PyQt and you can use this widget also in python. + +QHexEdit takes the data of a QByteArray (setData()) and shows it. You can use +the mouse or the keyboard to navigate inside the widget. If you hit the keys +(0..9, a..f) you will change the data. Changed data is highlighted and can be +accessed via data(). + +Normaly QHexEdit works in the overwrite Mode. You can set overwriteMode(false) +and insert data. In this case the size of data() increases. It is also possible +to delete bytes (del or backspace), here the size of data decreases. + +You can select data with keyboard hits or mouse movements. The copy-key will +copy the selected data into the clipboard. The cut-key copies also but delets +it afterwards. In overwrite mode, the paste function overwrites the content of +the (does not change the length) data. In insert mode, clipboard data will be +inserted. The clipboard content is expected in ASCII Hex notation. Unknown +characters will be ignored. + +QHexEdit comes with undo/redo functionality. All changes can be undone, by +pressing the undo-key (usually ctr-z). They can also be redone afterwards. +The undo/redo framework is cleared, when setData() sets up a new +content for the editor. You can search data inside the content with indexOf() +and lastIndexOf(). The replace() function is to change located subdata. This +'replaced' data can also be undone by the undo/redo framework. + +This widget can only handle small amounts of data. The size has to be below 10 +megabytes, otherwise the scroll sliders ard not shown and you can't scroll any +more. +*/ +class GUI_API_EXPORT QHexEdit : public QScrollArea +{ + Q_OBJECT + /*! Property data holds the content of QHexEdit. Call setData() to set the + content of QHexEdit, data() returns the actual content. + */ + Q_PROPERTY(QByteArray data READ data WRITE setData) + + /*! Property addressOffset is added to the Numbers of the Address Area. + A offset in the address area (left side) is sometimes usefull, whe you show + only a segment of a complete memory picture. With setAddressOffset() you set + this property - with addressOffset() you get the actual value. + */ + Q_PROPERTY(int addressOffset READ addressOffset WRITE setAddressOffset) + + /*! Property address area color sets (setAddressAreaColor()) the backgorund + color of address areas. You can also read the color (addressaAreaColor()). + */ + Q_PROPERTY(QColor addressAreaColor READ addressAreaColor WRITE setAddressAreaColor) + + /*! Porperty cursorPosition sets or gets the position of the editor cursor + in QHexEdit. + */ + Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition) + + /*! Property highlighting color sets (setHighlightingColor()) the backgorund + color of highlighted text areas. You can also read the color + (highlightingColor()). + */ + Q_PROPERTY(QColor highlightingColor READ highlightingColor WRITE setHighlightingColor) + + /*! Property selection color sets (setSelectionColor()) the backgorund + color of selected text areas. You can also read the color + (selectionColor()). + */ + Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor) + + /*! Porperty overwrite mode sets (setOverwriteMode()) or gets (overwriteMode()) the mode + in which the editor works. In overwrite mode the user will overwrite existing data. The + size of data will be constant. In insert mode the size will grow, when inserting + new data. + */ + Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode) + + /*! Porperty readOnly sets (setReadOnly()) or gets (isReadOnly) the mode + in which the editor works. In readonly mode the the user can only navigate + through the data and select data; modifying is not possible. This + property's default is false. + */ + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + + /*! Set the font of the widget. Please use fixed width fonts like Mono or Courier.*/ + Q_PROPERTY(QFont font READ font WRITE setFont) + + +public: + /*! Creates an instance of QHexEdit. + \param parent Parent widget of QHexEdit. + */ + QHexEdit(QWidget *parent = 0); + + /*! Returns the index position of the first occurrence + of the byte array ba in this byte array, searching forward from index position + from. Returns -1 if ba could not be found. In addition to this functionality + of QByteArray the cursorposition is set to the end of found bytearray and + it will be selected. + + */ + int indexOf(const QByteArray & ba, int from = 0) const; + + /*! Inserts a byte array. + \param i Index position, where to insert + \param ba byte array, which is to insert + In overwrite mode, the existing data will be overwritten, in insertmode ba will be + inserted and size of data grows. + */ + void insert(int i, const QByteArray & ba); + + /*! Inserts a char. + \param i Index position, where to insert + \param ch Char, which is to insert + In overwrite mode, the existing data will be overwritten, in insertmode ba will be + inserted and size of data grows. + */ + void insert(int i, char ch); + + /*! Returns the index position of the last occurrence + of the byte array ba in this byte array, searching backwards from index position + from. Returns -1 if ba could not be found. In addition to this functionality + of QByteArray the cursorposition is set to the beginning of found bytearray and + it will be selected. + + */ + int lastIndexOf(const QByteArray & ba, int from = 0) const; + + /*! Removes len bytes from the content. + \param pos Index position, where to remove + \param len Amount of bytes to remove + In overwrite mode, the existing bytes will be overwriten with 0x00. + */ + void remove(int pos, int len=1); + + /*! Replaces len bytes from index position pos with the byte array after. + */ + void replace( int pos, int len, const QByteArray & after); + + /*! Gives back a formatted image of the content of QHexEdit + */ + QString toReadableString(); + + /*! Gives back a formatted image of the selected content of QHexEdit + */ + QString selectionToReadableString(); + + /*! \cond docNever */ + void setAddressOffset(int offset); + int addressOffset(); + void setCursorPosition(int cusorPos); + int cursorPosition(); + void setData(QByteArray const &data); + QByteArray data(); + void setAddressAreaColor(QColor const &color); + QColor addressAreaColor(); + void setHighlightingColor(QColor const &color); + QColor highlightingColor(); + void setSelectionColor(QColor const &color); + QColor selectionColor(); + void setOverwriteMode(bool); + bool overwriteMode(); + void setReadOnly(bool); + bool isReadOnly(); + const QFont &font() const; + void setFont(const QFont &); + /*! \endcond docNever */ + +public slots: + /*! Redoes the last operation. If there is no operation to redo, i.e. + there is no redo step in the undo/redo history, nothing happens. + */ + void redo(); + + /*! Set the minimum width of the address area. + \param addressWidth Width in characters. + */ + void setAddressWidth(int addressWidth); + + /*! Switch the address area on or off. + \param addressArea true (show it), false (hide it). + */ + void setAddressArea(bool addressArea); + + /*! Switch the ascii area on or off. + \param asciiArea true (show it), false (hide it). + */ + void setAsciiArea(bool asciiArea); + + /*! Switch the highlighting feature on or of. + \param mode true (show it), false (hide it). + */ + void setHighlighting(bool mode); + + /*! Undoes the last operation. If there is no operation to undo, i.e. + there is no undo step in the undo/redo history, nothing happens. + */ + void undo(); + +signals: + + /*! Contains the address, where the cursor is located. */ + void currentAddressChanged(int address); + + /*! Contains the size of the data to edit. */ + void currentSizeChanged(int size); + + /*! The signal is emited every time, the data is changed. */ + void dataChanged(); + + /*! The signal is emited every time, the overwrite mode is changed. */ + void overwriteModeChanged(bool state); + +private: + /*! \cond docNever */ + QHexEditPrivate *qHexEdit_p; + QHBoxLayout *layout; + QScrollArea *scrollArea; + /*! \endcond docNever */ +}; + +#endif + diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp new file mode 100644 index 0000000..3919c19 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.cpp @@ -0,0 +1,883 @@ +#include +#include + +#include "qhexedit_p.h" +#include "commands.h" + +const int HEXCHARS_IN_LINE = 47; +const int GAP_ADR_HEX = 10; +const int GAP_HEX_ASCII = 16; +const int BYTES_PER_LINE = 16; + +QHexEditPrivate::QHexEditPrivate(QScrollArea *parent) : QWidget(parent) +{ + _undoStack = new QUndoStack(this); + + _scrollArea = parent; + setAddressWidth(4); + setAddressOffset(0); + setAddressArea(true); + setAsciiArea(true); + setHighlighting(true); + setOverwriteMode(true); + setReadOnly(false); + setAddressAreaColor(QColor(0xd4, 0xd4, 0xd4, 0xff)); + setHighlightingColor(QColor(0xee, 0xee, 0x88, 0xff)); + setSelectionColor(QColor(0x6d, 0x9e, 0xff, 0xff)); + setFont(QFont("Courier", 10)); + + _size = 0; + resetSelection(0); + + setFocusPolicy(Qt::StrongFocus); + + connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor())); + _cursorTimer.setInterval(500); + _cursorTimer.start(); +} + +void QHexEditPrivate::setAddressOffset(int offset) +{ + _xData.setAddressOffset(offset); + adjust(); +} + +int QHexEditPrivate::addressOffset() +{ + return _xData.addressOffset(); +} + +void QHexEditPrivate::setData(const QByteArray &data) +{ + _xData.setData(data); + _undoStack->clear(); + adjust(); + setCursorPos(0); +} + +QByteArray QHexEditPrivate::data() +{ + return _xData.data(); +} + +void QHexEditPrivate::setAddressAreaColor(const QColor &color) +{ + _addressAreaColor = color; + update(); +} + +QColor QHexEditPrivate::addressAreaColor() +{ + return _addressAreaColor; +} + +void QHexEditPrivate::setHighlightingColor(const QColor &color) +{ + _highlightingColor = color; + update(); +} + +QColor QHexEditPrivate::highlightingColor() +{ + return _highlightingColor; +} + +void QHexEditPrivate::setSelectionColor(const QColor &color) +{ + _selectionColor = color; + update(); +} + +QColor QHexEditPrivate::selectionColor() +{ + return _selectionColor; +} + +void QHexEditPrivate::setReadOnly(bool readOnly) +{ + _readOnly = readOnly; +} + +bool QHexEditPrivate::isReadOnly() +{ + return _readOnly; +} + +XByteArray & QHexEditPrivate::xData() +{ + return _xData; +} + +int QHexEditPrivate::indexOf(const QByteArray & ba, int from) +{ + if (from > (_xData.data().length() - 1)) + from = _xData.data().length() - 1; + int idx = _xData.data().indexOf(ba, from); + if (idx > -1) + { + int curPos = idx*2; + setCursorPos(curPos + ba.length()*2); + resetSelection(curPos); + setSelection(curPos + ba.length()*2); + ensureVisible(); + } + return idx; +} + +void QHexEditPrivate::insert(int index, const QByteArray & ba) +{ + if (ba.length() > 0) + { + if (_overwriteMode) + { + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); + _undoStack->push(arrayCommand); + emit dataChanged(); + } + else + { + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::insert, index, ba, ba.length()); + _undoStack->push(arrayCommand); + emit dataChanged(); + } + } +} + +void QHexEditPrivate::insert(int index, char ch) +{ + QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::insert, index, ch); + _undoStack->push(charCommand); + emit dataChanged(); +} + +int QHexEditPrivate::lastIndexOf(const QByteArray & ba, int from) +{ + from -= ba.length(); + if (from < 0) + from = 0; + int idx = _xData.data().lastIndexOf(ba, from); + if (idx > -1) + { + int curPos = idx*2; + setCursorPos(curPos); + resetSelection(curPos); + setSelection(curPos + ba.length()*2); + ensureVisible(); + } + return idx; +} + +void QHexEditPrivate::remove(int index, int len) +{ + if (len > 0) + { + if (len == 1) + { + if (_overwriteMode) + { + QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, char(0)); + _undoStack->push(charCommand); + emit dataChanged(); + } + else + { + QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::remove, index, char(0)); + _undoStack->push(charCommand); + emit dataChanged(); + } + } + else + { + QByteArray ba = QByteArray(len, char(0)); + if (_overwriteMode) + { + QUndoCommand *arrayCommand = new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); + _undoStack->push(arrayCommand); + emit dataChanged(); + } + else + { + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::remove, index, ba, len); + _undoStack->push(arrayCommand); + emit dataChanged(); + } + } + } +} + +void QHexEditPrivate::replace(int index, char ch) +{ + QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, ch); + _undoStack->push(charCommand); + resetSelection(); + emit dataChanged(); +} + +void QHexEditPrivate::replace(int index, const QByteArray & ba) +{ + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); + _undoStack->push(arrayCommand); + resetSelection(); + emit dataChanged(); +} + +void QHexEditPrivate::replace(int pos, int len, const QByteArray &after) +{ + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, pos, after, len); + _undoStack->push(arrayCommand); + resetSelection(); + emit dataChanged(); +} + +void QHexEditPrivate::setAddressArea(bool addressArea) +{ + _addressArea = addressArea; + adjust(); + + setCursorPos(_cursorPosition); +} + +void QHexEditPrivate::setAddressWidth(int addressWidth) +{ + _xData.setAddressWidth(addressWidth); + + setCursorPos(_cursorPosition); +} + +void QHexEditPrivate::setAsciiArea(bool asciiArea) +{ + _asciiArea = asciiArea; + adjust(); +} + +void QHexEditPrivate::setFont(const QFont &font) +{ + QWidget::setFont(font); + adjust(); +} + +void QHexEditPrivate::setHighlighting(bool mode) +{ + _highlighting = mode; + update(); +} + +void QHexEditPrivate::setOverwriteMode(bool overwriteMode) +{ + _overwriteMode = overwriteMode; +} + +bool QHexEditPrivate::overwriteMode() +{ + return _overwriteMode; +} + +void QHexEditPrivate::redo() +{ + _undoStack->redo(); + emit dataChanged(); + setCursorPos(_cursorPosition); + update(); +} + +void QHexEditPrivate::undo() +{ + _undoStack->undo(); + emit dataChanged(); + setCursorPos(_cursorPosition); + update(); +} + +QString QHexEditPrivate::toRedableString() +{ + return _xData.toRedableString(); +} + + +QString QHexEditPrivate::selectionToReadableString() +{ + return _xData.toRedableString(getSelectionBegin(), getSelectionEnd()); +} + +void QHexEditPrivate::keyPressEvent(QKeyEvent *event) +{ + int charX = (_cursorX - _xPosHex) / _charWidth; + int posX = (charX / 3) * 2 + (charX % 3); + int posBa = (_cursorY / _charHeight) * BYTES_PER_LINE + posX / 2; + + +/*****************************************************************************/ +/* Cursor movements */ +/*****************************************************************************/ + + if (event->matches(QKeySequence::MoveToNextChar)) + { + setCursorPos(_cursorPosition + 1); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousChar)) + { + setCursorPos(_cursorPosition - 1); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToEndOfLine)) + { + setCursorPos(_cursorPosition | (2 * BYTES_PER_LINE -1)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfLine)) + { + setCursorPos(_cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE))); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousLine)) + { + setCursorPos(_cursorPosition - (2 * BYTES_PER_LINE)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToNextLine)) + { + setCursorPos(_cursorPosition + (2 * BYTES_PER_LINE)); + resetSelection(_cursorPosition); + } + + if (event->matches(QKeySequence::MoveToNextPage)) + { + setCursorPos(_cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousPage)) + { + setCursorPos(_cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToEndOfDocument)) + { + setCursorPos(_xData.size() * 2); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfDocument)) + { + setCursorPos(0); + resetSelection(_cursorPosition); + } + +/*****************************************************************************/ +/* Select commands */ +/*****************************************************************************/ + if (event->matches(QKeySequence::SelectAll)) + { + resetSelection(0); + setSelection(2*_xData.size() + 1); + } + if (event->matches(QKeySequence::SelectNextChar)) + { + int pos = _cursorPosition + 1; + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousChar)) + { + int pos = _cursorPosition - 1; + setSelection(pos); + setCursorPos(pos); + } + if (event->matches(QKeySequence::SelectEndOfLine)) + { + int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfLine)) + { + int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousLine)) + { + int pos = _cursorPosition - (2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectNextLine)) + { + int pos = _cursorPosition + (2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + + if (event->matches(QKeySequence::SelectNextPage)) + { + int pos = _cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousPage)) + { + int pos = _cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectEndOfDocument)) + { + int pos = _xData.size() * 2; + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfDocument)) + { + int pos = 0; + setCursorPos(pos); + setSelection(pos); + } + +/*****************************************************************************/ +/* Edit Commands */ +/*****************************************************************************/ +if (!_readOnly) +{ + /* Hex input */ + int key = int(event->text()[0].toLatin1()); // changed from toAscii() to toLatin() for Qt5 needs + if ((key>='0' && key<='9') || (key>='a' && key <= 'f')) + { + if (getSelectionBegin() != getSelectionEnd()) + { + posBa = getSelectionBegin(); + remove(posBa, getSelectionEnd() - posBa); + setCursorPos(2*posBa); + resetSelection(2*posBa); + } + + // If insert mode, then insert a byte + if (_overwriteMode == false) + if ((charX % 3) == 0) + { + insert(posBa, char(0)); + } + + // Change content + if (_xData.size() > 0) + { + QByteArray hexValue = _xData.data().mid(posBa, 1).toHex(); + if ((charX % 3) == 0) + hexValue[0] = key; + else + hexValue[1] = key; + + replace(posBa, QByteArray().fromHex(hexValue)[0]); + + setCursorPos(_cursorPosition + 1); + resetSelection(_cursorPosition); + } + } + + /* Cut & Paste */ + if (event->matches(QKeySequence::Cut)) + { + QString result = QString(); + for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++) + { + result += _xData.data().mid(idx, 1).toHex() + " "; + if ((idx % 16) == 15) + result.append("\n"); + } + remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(result); + setCursorPos(getSelectionBegin()); + resetSelection(getSelectionBegin()); + } + + if (event->matches(QKeySequence::Paste)) + { + QClipboard *clipboard = QApplication::clipboard(); + QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1()); + insert(_cursorPosition / 2, ba); + setCursorPos(_cursorPosition + 2 * ba.length()); + resetSelection(getSelectionBegin()); + } + + + /* Delete char */ + if (event->matches(QKeySequence::Delete)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + posBa = getSelectionBegin(); + remove(posBa, getSelectionEnd() - posBa); + setCursorPos(2*posBa); + resetSelection(2*posBa); + } + else + { + if (_overwriteMode) + replace(posBa, char(0)); + else + remove(posBa, 1); + } + } + + /* Backspace */ + if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + posBa = getSelectionBegin(); + remove(posBa, getSelectionEnd() - posBa); + setCursorPos(2*posBa); + resetSelection(2*posBa); + } + else + { + if (posBa > 0) + { + if (_overwriteMode) + replace(posBa - 1, char(0)); + else + remove(posBa - 1, 1); + setCursorPos(_cursorPosition - 2); + } + } + } + + /* undo */ + if (event->matches(QKeySequence::Undo)) + { + undo(); + } + + /* redo */ + if (event->matches(QKeySequence::Redo)) + { + redo(); + } + + } + + if (event->matches(QKeySequence::Copy)) + { + QString result = QString(); + for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++) + { + result += _xData.data().mid(idx, 1).toHex() + " "; + if ((idx % 16) == 15) + result.append('\n'); + } + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(result); + } + + // Switch between insert/overwrite mode + if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier)) + { + _overwriteMode = !_overwriteMode; + setCursorPos(_cursorPosition); + overwriteModeChanged(_overwriteMode); + } + + ensureVisible(); + update(); +} + +void QHexEditPrivate::mouseMoveEvent(QMouseEvent * event) +{ + _blink = false; + update(); + int actPos = cursorPos(event->pos()); + setCursorPos(actPos); + setSelection(actPos); +} + +void QHexEditPrivate::mousePressEvent(QMouseEvent * event) +{ + _blink = false; + update(); + int cPos = cursorPos(event->pos()); + resetSelection(cPos); + setCursorPos(cPos); +} + +void QHexEditPrivate::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + + // draw some patterns if needed + painter.fillRect(event->rect(), this->palette().color(QPalette::Base)); + if (_addressArea) + painter.fillRect(QRect(_xPosAdr, event->rect().top(), _xPosHex - GAP_ADR_HEX + 2, height()), _addressAreaColor); + if (_asciiArea) + { + int linePos = _xPosAscii - (GAP_HEX_ASCII / 2); + painter.setPen(Qt::gray); + painter.drawLine(linePos, event->rect().top(), linePos, height()); + } + + painter.setPen(this->palette().color(QPalette::WindowText)); + + // calc position + int firstLineIdx = ((event->rect().top()/ _charHeight) - _charHeight) * BYTES_PER_LINE; + if (firstLineIdx < 0) + firstLineIdx = 0; + int lastLineIdx = ((event->rect().bottom() / _charHeight) + _charHeight) * BYTES_PER_LINE; + if (lastLineIdx > _xData.size()) + lastLineIdx = _xData.size(); + int yPosStart = ((firstLineIdx) / BYTES_PER_LINE) * _charHeight + _charHeight; + + // paint address area + if (_addressArea) + { + for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) + { + QString address = QString("%1") + .arg(lineIdx + _xData.addressOffset(), _xData.realAddressNumbers(), 16, QChar('0')); + painter.drawText(_xPosAdr, yPos, address); + } + } + + // paint hex area + QByteArray hexBa(_xData.data().mid(firstLineIdx, lastLineIdx - firstLineIdx + 1).toHex()); + QBrush highLighted = QBrush(_highlightingColor); + QPen colHighlighted = QPen(this->palette().color(QPalette::WindowText)); + QBrush selected = QBrush(_selectionColor); + QPen colSelected = QPen(Qt::white); + QPen colStandard = QPen(this->palette().color(QPalette::WindowText)); + painter.setBackgroundMode(Qt::TransparentMode); + + for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) + { + QByteArray hex; + int xPos = _xPosHex; + for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++) + { + int posBa = lineIdx + colIdx; + if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa)) + { + painter.setBackground(selected); + painter.setBackgroundMode(Qt::OpaqueMode); + painter.setPen(colSelected); + } + else + { + if (_highlighting) + { + // hilight diff bytes + painter.setBackground(highLighted); + if (_xData.dataChanged(posBa)) + { + painter.setPen(colHighlighted); + painter.setBackgroundMode(Qt::OpaqueMode); + } + else + { + painter.setPen(colStandard); + painter.setBackgroundMode(Qt::TransparentMode); + } + } + } + + // render hex value + if (colIdx == 0) + { + hex = hexBa.mid((lineIdx - firstLineIdx) * 2, 2); + painter.drawText(xPos, yPos, hex); + xPos += 2 * _charWidth; + } else { + hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2).prepend(" "); + painter.drawText(xPos, yPos, hex); + xPos += 3 * _charWidth; + } + + } + } + painter.setBackgroundMode(Qt::TransparentMode); + painter.setPen(this->palette().color(QPalette::WindowText)); + + // paint ascii area + if (_asciiArea) + { + painter.setBackground(highLighted); + for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) + { + int xPosAscii = _xPosAscii; + for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++) + { + if (_cursorPosition/2 == (lineIdx + colIdx)) + { + painter.setBackgroundMode(Qt::OpaqueMode); + painter.setPen(colHighlighted); + } + else + { + painter.setPen(colStandard); + painter.setBackgroundMode(Qt::TransparentMode); + } + + painter.drawText(xPosAscii, yPos, _xData.asciiChar(lineIdx + colIdx)); + xPosAscii += _charWidth; + } + } + } + painter.setBackgroundMode(Qt::TransparentMode); + painter.setPen(this->palette().color(QPalette::WindowText)); + + // paint cursor + if (_blink && !_readOnly && hasFocus()) + { + if (_overwriteMode) + painter.fillRect(_cursorX, _cursorY + _charHeight - 2, _charWidth, 2, this->palette().color(QPalette::WindowText)); + else + painter.fillRect(_cursorX, _cursorY, 2, _charHeight, this->palette().color(QPalette::WindowText)); + } + + if (_size != _xData.size()) + { + _size = _xData.size(); + emit currentSizeChanged(_size); + } +} + +void QHexEditPrivate::setCursorPos(int position) +{ + // delete cursor + _blink = false; + update(); + + // cursor in range? + if (_overwriteMode) + { + if (position > (_xData.size() * 2 - 1)) + position = _xData.size() * 2 - 1; + } else { + if (position > (_xData.size() * 2)) + position = _xData.size() * 2; + } + + if (position < 0) + position = 0; + + // calc position + _cursorPosition = position; + _cursorY = (position / (2 * BYTES_PER_LINE)) * _charHeight + 4; + int x = (position % (2 * BYTES_PER_LINE)); + _cursorX = (((x / 2) * 3) + (x % 2)) * _charWidth + _xPosHex; + + // immiadately draw cursor + _blink = true; + update(); + emit currentAddressChanged(_cursorPosition/2); +} + +int QHexEditPrivate::cursorPos(QPoint pos) +{ + int result = -1; + // find char under cursor + if ((pos.x() >= _xPosHex) and (pos.x() < (_xPosHex + HEXCHARS_IN_LINE * _charWidth))) + { + int x = (pos.x() - _xPosHex) / _charWidth; + if ((x % 3) == 0) + x = (x / 3) * 2; + else + x = ((x / 3) * 2) + 1; + int y = ((pos.y() - 3) / _charHeight) * 2 * BYTES_PER_LINE; + result = x + y; + } + return result; +} + +int QHexEditPrivate::cursorPos() +{ + return _cursorPosition; +} + +void QHexEditPrivate::resetSelection() +{ + _selectionBegin = _selectionInit; + _selectionEnd = _selectionInit; +} + +void QHexEditPrivate::resetSelection(int pos) +{ + if (pos < 0) + pos = 0; + pos = pos / 2; + _selectionInit = pos; + _selectionBegin = pos; + _selectionEnd = pos; +} + +void QHexEditPrivate::setSelection(int pos) +{ + if (pos < 0) + pos = 0; + pos = pos / 2; + if (pos >= _selectionInit) + { + _selectionEnd = pos; + _selectionBegin = _selectionInit; + } + else + { + _selectionBegin = pos; + _selectionEnd = _selectionInit; + } +} + +int QHexEditPrivate::getSelectionBegin() +{ + return _selectionBegin; +} + +int QHexEditPrivate::getSelectionEnd() +{ + return _selectionEnd; +} + +QSize QHexEditPrivate::sizeHint() const +{ + int wd = _xPosAscii + BYTES_PER_LINE * _charWidth; + int hg = _charHeight * 8; + if (!_asciiArea) + wd = _xPosHex + HEXCHARS_IN_LINE * _charWidth; + + return QSize(wd, hg); +} + + +void QHexEditPrivate::updateCursor() +{ + if (_blink) + _blink = false; + else + _blink = true; + update(_cursorX, _cursorY, _charWidth, _charHeight); +} + +void QHexEditPrivate::adjust() +{ + _charWidth = fontMetrics().width(QLatin1Char('9')); + _charHeight = fontMetrics().height(); + + _xPosAdr = 0; + if (_addressArea) + _xPosHex = _xData.realAddressNumbers()*_charWidth + GAP_ADR_HEX; + else + _xPosHex = 0; + _xPosAscii = _xPosHex + HEXCHARS_IN_LINE * _charWidth + GAP_HEX_ASCII; + + // tell QAbstractScollbar, how big we are + setMinimumHeight(((_xData.size()/16 + 1) * _charHeight) + 5); + if(_asciiArea) + setMinimumWidth(_xPosAscii + (BYTES_PER_LINE * _charWidth)); + else + setMinimumWidth(_xPosHex + HEXCHARS_IN_LINE * _charWidth); + + update(); +} + +void QHexEditPrivate::ensureVisible() +{ + // scrolls to cursorx, cusory (which are set by setCursorPos) + // x-margin is 3 pixels, y-margin is half of charHeight + _scrollArea->ensureVisible(_cursorX, _cursorY + _charHeight/2, 3, _charHeight/2 + 2); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h new file mode 100644 index 0000000..b9c17d0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/qhexedit_p.h @@ -0,0 +1,129 @@ +#ifndef QHEXEDIT_P_H +#define QHEXEDIT_P_H + +/** \cond docNever */ + + +#include +#include +#include +#include "xbytearray.h" + +class QHexEditPrivate : public QWidget +{ +Q_OBJECT + +public: + explicit QHexEditPrivate(QScrollArea *parent); + + void setAddressAreaColor(QColor const &color); + QColor addressAreaColor(); + + void setAddressOffset(int offset); + int addressOffset(); + + void setCursorPos(int position); + int cursorPos(); + + void setData(QByteArray const &data); + QByteArray data(); + + void setHighlightingColor(QColor const &color); + QColor highlightingColor(); + + void setOverwriteMode(bool overwriteMode); + bool overwriteMode(); + + void setReadOnly(bool readOnly); + bool isReadOnly(); + + void setSelectionColor(QColor const &color); + QColor selectionColor(); + + XByteArray & xData(); + + int indexOf(const QByteArray & ba, int from = 0); + void insert(int index, const QByteArray & ba); + void insert(int index, char ch); + int lastIndexOf(const QByteArray & ba, int from = 0); + void remove(int index, int len=1); + void replace(int index, char ch); + void replace(int index, const QByteArray & ba); + void replace(int pos, int len, const QByteArray & after); + + void setAddressArea(bool addressArea); + void setAddressWidth(int addressWidth); + void setAsciiArea(bool asciiArea); + void setHighlighting(bool mode); + virtual void setFont(const QFont &font); + + void undo(); + void redo(); + + QString toRedableString(); + QString selectionToReadableString(); + + QSize sizeHint() const; + +signals: + void currentAddressChanged(int address); + void currentSizeChanged(int size); + void dataChanged(); + void overwriteModeChanged(bool state); + +protected: + void keyPressEvent(QKeyEvent * event); + void mouseMoveEvent(QMouseEvent * event); + void mousePressEvent(QMouseEvent * event); + + void paintEvent(QPaintEvent *event); + + int cursorPos(QPoint pos); // calc cursorpos from graphics position. DOES NOT STORE POSITION + + void resetSelection(int pos); // set selectionStart and selectionEnd to pos + void resetSelection(); // set selectionEnd to selectionStart + void setSelection(int pos); // set min (if below init) or max (if greater init) + int getSelectionBegin(); + int getSelectionEnd(); + + +private slots: + void updateCursor(); + +private: + void adjust(); + void ensureVisible(); + + QColor _addressAreaColor; + QColor _highlightingColor; + QColor _selectionColor; + QScrollArea *_scrollArea; + QTimer _cursorTimer; + QUndoStack *_undoStack; + + XByteArray _xData; // Hält den Inhalt des Hex Editors + + bool _blink; // true: then cursor blinks + bool _renderingRequired; // Flag to store that rendering is necessary + bool _addressArea; // left area of QHexEdit + bool _asciiArea; // medium area + bool _highlighting; // highlighting of changed bytes + bool _overwriteMode; + bool _readOnly; // true: the user can only look and navigate + + int _charWidth, _charHeight; // char dimensions (dpendend on font) + int _cursorX, _cursorY; // graphics position of the cursor + int _cursorPosition; // character positioin in stream (on byte ends in to steps) + int _xPosAdr, _xPosHex, _xPosAscii; // graphics x-position of the areas + + int _selectionBegin; // First selected char + int _selectionEnd; // Last selected char + int _selectionInit; // That's, where we pressed the mouse button + + int _size; +}; + +/** \endcond docNever */ + +#endif + diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp new file mode 100644 index 0000000..ec8bf3d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.cpp @@ -0,0 +1,167 @@ +#include "xbytearray.h" + +XByteArray::XByteArray() +{ + _oldSize = -99; + _addressNumbers = 4; + _addressOffset = 0; + +} + +int XByteArray::addressOffset() +{ + return _addressOffset; +} + +void XByteArray::setAddressOffset(int offset) +{ + _addressOffset = offset; +} + +int XByteArray::addressWidth() +{ + return _addressNumbers; +} + +void XByteArray::setAddressWidth(int width) +{ + if ((width >= 0) and (width<=6)) + { + _addressNumbers = width; + } +} + +QByteArray & XByteArray::data() +{ + return _data; +} + +void XByteArray::setData(QByteArray data) +{ + _data = data; + _changedData = QByteArray(data.length(), char(0)); +} + +bool XByteArray::dataChanged(int i) +{ + return bool(_changedData[i]); +} + +QByteArray XByteArray::dataChanged(int i, int len) +{ + return _changedData.mid(i, len); +} + +void XByteArray::setDataChanged(int i, bool state) +{ + _changedData[i] = char(state); +} + +void XByteArray::setDataChanged(int i, const QByteArray & state) +{ + int length = state.length(); + int len; + if ((i + length) > _changedData.length()) + len = _changedData.length() - i; + else + len = length; + _changedData.replace(i, len, state); +} + +int XByteArray::realAddressNumbers() +{ + if (_oldSize != _data.size()) + { + // is addressNumbers wide enought? + QString test = QString("%1") + .arg(_data.size() + _addressOffset, _addressNumbers, 16, QChar('0')); + _realAddressNumbers = test.size(); + } + return _realAddressNumbers; +} + +int XByteArray::size() +{ + return _data.size(); +} + +QByteArray & XByteArray::insert(int i, char ch) +{ + _data.insert(i, ch); + _changedData.insert(i, char(1)); + return _data; +} + +QByteArray & XByteArray::insert(int i, const QByteArray & ba) +{ + _data.insert(i, ba); + _changedData.insert(i, QByteArray(ba.length(), char(1))); + return _data; +} + +QByteArray & XByteArray::remove(int i, int len) +{ + _data.remove(i, len); + _changedData.remove(i, len); + return _data; +} + +QByteArray & XByteArray::replace(int index, char ch) +{ + _data[index] = ch; + _changedData[index] = char(1); + return _data; +} + +QByteArray & XByteArray::replace(int index, const QByteArray & ba) +{ + int len = ba.length(); + return replace(index, len, ba); +} + +QByteArray & XByteArray::replace(int index, int length, const QByteArray & ba) +{ + int len; + if ((index + length) > _data.length()) + len = _data.length() - index; + else + len = length; + _data.replace(index, len, ba.mid(0, len)); + _changedData.replace(index, len, QByteArray(len, char(1))); + return _data; +} + +QChar XByteArray::asciiChar(int index) +{ + char ch = _data[index]; + if ((ch < 0x20) or (ch > 0x7e)) + ch = '.'; + return QChar(ch); +} + +QString XByteArray::toRedableString(int start, int end) +{ + int adrWidth = realAddressNumbers(); + if (_addressNumbers > adrWidth) + adrWidth = _addressNumbers; + if (end < 0) + end = _data.size(); + + QString result; + for (int i=start; i < end; i += 16) + { + QString adrStr = QString("%1").arg(_addressOffset + i, adrWidth, 16, QChar('0')); + QString hexStr; + QString ascStr; + for (int j=0; j<16; j++) + { + if ((i + j) < _data.size()) + { + hexStr.append(" ").append(_data.mid(i+j, 1).toHex()); + ascStr.append(asciiChar(i+j)); + } + } + result += adrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n"; + } + return result; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h new file mode 100644 index 0000000..a5cdc11 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qhexedit2/xbytearray.h @@ -0,0 +1,67 @@ +#ifndef XBYTEARRAY_H +#define XBYTEARRAY_H + +/** \cond docNever */ + +#include "guiSQLiteStudio_global.h" +#include + +/*! XByteArray represents the content of QHexEcit. +XByteArray comprehend the data itself and informations to store if it was +changed. The QHexEdit component uses these informations to perform nice +rendering of the data + +XByteArray also provides some functionality to insert, replace and remove +single chars and QByteArras. Additionally some functions support rendering +and converting to readable strings. +*/ +class GUI_API_EXPORT XByteArray +{ +public: + explicit XByteArray(); + + int addressOffset(); + void setAddressOffset(int offset); + + int addressWidth(); + void setAddressWidth(int width); + + QByteArray & data(); + void setData(QByteArray data); + + bool dataChanged(int i); + QByteArray dataChanged(int i, int len); + void setDataChanged(int i, bool state); + void setDataChanged(int i, const QByteArray & state); + + int realAddressNumbers(); + int size(); + + QByteArray & insert(int i, char ch); + QByteArray & insert(int i, const QByteArray & ba); + + QByteArray & remove(int pos, int len); + + QByteArray & replace(int index, char ch); + QByteArray & replace(int index, const QByteArray & ba); + QByteArray & replace(int index, int length, const QByteArray & ba); + + QChar asciiChar(int index); + QString toRedableString(int start=0, int end=-1); + +signals: + +public slots: + +private: + QByteArray _data; + QByteArray _changedData; + + int _addressNumbers; // wanted width of address area + int _addressOffset; // will be added to the real addres inside bytearray + int _realAddressNumbers; // real width of address area (can be greater then wanted width) + int _oldSize; // size of data +}; + +/** \endcond docNever */ +#endif // XBYTEARRAY_H diff --git a/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp b/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp new file mode 100644 index 0000000..6ca5c59 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.cpp @@ -0,0 +1,359 @@ +#include "qtscriptsyntaxhighlighter.h" +#include "uiconfig.h" + +#include + +JavaScriptSyntaxHighlighter::JavaScriptSyntaxHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) + , m_markCaseSensitivity(Qt::CaseInsensitive) +{ + // https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words + m_keywords << "break"; + m_keywords << "case"; + m_keywords << "catch"; + m_keywords << "continue"; + m_keywords << "default"; + m_keywords << "delete"; + m_keywords << "do"; + m_keywords << "else"; + m_keywords << "finally"; + m_keywords << "for"; + m_keywords << "function"; + m_keywords << "if"; + m_keywords << "in"; + m_keywords << "instanceof"; + m_keywords << "new"; + m_keywords << "return"; + m_keywords << "switch"; + m_keywords << "this"; + m_keywords << "throw"; + m_keywords << "try"; + m_keywords << "typeof"; + m_keywords << "var"; + m_keywords << "void"; + m_keywords << "while"; + m_keywords << "with"; + + m_keywords << "true"; + m_keywords << "false"; + m_keywords << "null"; + + // built-in and other popular objects + properties + m_knownIds << "Object"; + m_knownIds << "prototype"; + m_knownIds << "create"; + m_knownIds << "defineProperty"; + m_knownIds << "defineProperties"; + m_knownIds << "getOwnPropertyDescriptor"; + m_knownIds << "keys"; + m_knownIds << "getOwnPropertyNames"; + m_knownIds << "constructor"; + m_knownIds << "__parent__"; + m_knownIds << "__proto__"; + m_knownIds << "__defineGetter__"; + m_knownIds << "__defineSetter__"; + m_knownIds << "eval"; + m_knownIds << "hasOwnProperty"; + m_knownIds << "isPrototypeOf"; + m_knownIds << "__lookupGetter__"; + m_knownIds << "__lookupSetter__"; + m_knownIds << "__noSuchMethod__"; + m_knownIds << "propertyIsEnumerable"; + m_knownIds << "toSource"; + m_knownIds << "toLocaleString"; + m_knownIds << "toString"; + m_knownIds << "unwatch"; + m_knownIds << "valueOf"; + m_knownIds << "watch"; + + m_knownIds << "Function"; + m_knownIds << "arguments"; + m_knownIds << "arity"; + m_knownIds << "caller"; + m_knownIds << "constructor"; + m_knownIds << "length"; + m_knownIds << "name"; + m_knownIds << "apply"; + m_knownIds << "bind"; + m_knownIds << "call"; + + m_knownIds << "String"; + m_knownIds << "fromCharCode"; + m_knownIds << "length"; + m_knownIds << "charAt"; + m_knownIds << "charCodeAt"; + m_knownIds << "concat"; + m_knownIds << "indexOf"; + m_knownIds << "lastIndexOf"; + m_knownIds << "localCompare"; + m_knownIds << "match"; + m_knownIds << "quote"; + m_knownIds << "replace"; + m_knownIds << "search"; + m_knownIds << "slice"; + m_knownIds << "split"; + m_knownIds << "substr"; + m_knownIds << "substring"; + m_knownIds << "toLocaleLowerCase"; + m_knownIds << "toLocaleUpperCase"; + m_knownIds << "toLowerCase"; + m_knownIds << "toUpperCase"; + m_knownIds << "trim"; + m_knownIds << "trimLeft"; + m_knownIds << "trimRight"; + + m_knownIds << "Array"; + m_knownIds << "isArray"; + m_knownIds << "index"; + m_knownIds << "input"; + m_knownIds << "pop"; + m_knownIds << "push"; + m_knownIds << "reverse"; + m_knownIds << "shift"; + m_knownIds << "sort"; + m_knownIds << "splice"; + m_knownIds << "unshift"; + m_knownIds << "concat"; + m_knownIds << "join"; + m_knownIds << "filter"; + m_knownIds << "forEach"; + m_knownIds << "every"; + m_knownIds << "map"; + m_knownIds << "some"; + m_knownIds << "reduce"; + m_knownIds << "reduceRight"; + + m_knownIds << "RegExp"; + m_knownIds << "global"; + m_knownIds << "ignoreCase"; + m_knownIds << "lastIndex"; + m_knownIds << "multiline"; + m_knownIds << "source"; + m_knownIds << "exec"; + m_knownIds << "test"; + + m_knownIds << "JSON"; + m_knownIds << "parse"; + m_knownIds << "stringify"; + + m_knownIds << "decodeURI"; + m_knownIds << "decodeURIComponent"; + m_knownIds << "encodeURI"; + m_knownIds << "encodeURIComponent"; + m_knownIds << "eval"; + m_knownIds << "isFinite"; + m_knownIds << "isNaN"; + m_knownIds << "parseFloat"; + m_knownIds << "parseInt"; + m_knownIds << "Infinity"; + m_knownIds << "NaN"; + m_knownIds << "undefined"; + + m_knownIds << "Math"; + m_knownIds << "E"; + m_knownIds << "LN2"; + m_knownIds << "LN10"; + m_knownIds << "LOG2E"; + m_knownIds << "LOG10E"; + m_knownIds << "PI"; + m_knownIds << "SQRT1_2"; + m_knownIds << "SQRT2"; + m_knownIds << "abs"; + m_knownIds << "acos"; + m_knownIds << "asin"; + m_knownIds << "atan"; + m_knownIds << "atan2"; + m_knownIds << "ceil"; + m_knownIds << "cos"; + m_knownIds << "exp"; + m_knownIds << "floor"; + m_knownIds << "log"; + m_knownIds << "max"; + m_knownIds << "min"; + m_knownIds << "pow"; + m_knownIds << "random"; + m_knownIds << "round"; + m_knownIds << "sin"; + m_knownIds << "sqrt"; + m_knownIds << "tan"; + + m_knownIds << "document"; + m_knownIds << "window"; + m_knownIds << "navigator"; + m_knownIds << "userAgent"; + + keywordsFormat.setFontWeight(QFont::Bold); +} + +void JavaScriptSyntaxHighlighter::highlightBlock(const QString &text) +{ + // parsing state + enum { + Start = -1, + Number = 1, + Identifier = 2, + String = 3, + Comment = 4, + Regex = 5 + }; + + int state = previousBlockState(); + int start = 0; + int i = 0; + while (i <= text.length()) { + QChar ch = (i < text.length()) ? text.at(i) : QChar(); + QChar next = (i < text.length() - 1) ? text.at(i + 1) : QChar(); + + switch (state) { + + case Start: + start = i; + if (ch.isSpace()) { + ++i; + } else if (ch.isDigit()) { + ++i; + state = Number; + } else if (ch.isLetter() || ch == '_') { + ++i; + state = Identifier; + } else if (ch == '\'' || ch == '\"') { + ++i; + state = String; + } else if (ch == '/' && next == '*') { + ++i; + ++i; + state = Comment; + } else if (ch == '/' && next == '/') { + i = text.length(); + setFormat(start, text.length(), CFG_UI.Colors.JavaScriptComment.get()); + } else if (ch == '/' && next != '*') { + ++i; + state = Regex; + } else { + if (!QString("(){}[]").contains(ch)) + setFormat(start, 1, CFG_UI.Colors.JavaScriptOperator.get()); + ++i; + state = Start; + } + break; + + case Number: + if (ch.isSpace() || !ch.isDigit()) { + setFormat(start, i - start, CFG_UI.Colors.JavaScriptNumber.get()); + state = Start; + } else { + ++i; + } + break; + + case Identifier: + if (ch.isSpace() || !(ch.isDigit() || ch.isLetter() || ch == '_')) { + QString token = text.mid(start, i - start).trimmed(); + if (m_keywords.contains(token)) + { + keywordsFormat.setForeground(CFG_UI.Colors.JavaScriptKeyword.get()); + setFormat(start, i - start, keywordsFormat); + } + else if (m_knownIds.contains(token)) + setFormat(start, i - start, CFG_UI.Colors.JavaScriptBuiltIn.get()); + state = Start; + } else { + ++i; + } + break; + + case String: + if (ch == text.at(start)) { + QChar prev = (i > 0) ? text.at(i - 1) : QChar(); + if (prev != '\\') { + ++i; + setFormat(start, i - start, CFG_UI.Colors.JavaScriptString.get()); + state = Start; + } else { + ++i; + } + } else { + ++i; + } + break; + + case Comment: + if (ch == '*' && next == '/') { + ++i; + ++i; + setFormat(start, i - start, CFG_UI.Colors.JavaScriptComment.get()); + state = Start; + } else { + ++i; + } + break; + + case Regex: + if (ch == '/') { + QChar prev = (i > 0) ? text.at(i - 1) : QChar(); + if (prev != '\\') { + ++i; + setFormat(start, i - start, CFG_UI.Colors.JavaScriptString.get()); + state = Start; + } else { + ++i; + } + } else { + ++i; + } + break; + + default: + state = Start; + break; + } + } + + if (state == Comment) + setFormat(start, text.length(), CFG_UI.Colors.JavaScriptComment.get()); + else + state = Start; + + if (!m_markString.isEmpty()) { + int pos = 0; + int len = m_markString.length(); + QTextCharFormat markerFormat; + markerFormat.setBackground(CFG_UI.Colors.JavaScriptMarker.get()); + markerFormat.setForeground(CFG_UI.Colors.JavaScriptFg.get()); + for (;;) { + pos = text.indexOf(m_markString, pos, m_markCaseSensitivity); + if (pos < 0) + break; + setFormat(pos, len, markerFormat); + ++pos; + } + } + + setCurrentBlockState(state); +} + +void JavaScriptSyntaxHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity) +{ + m_markString = str; + m_markCaseSensitivity = caseSensitivity; + rehighlight(); +} + + +QString JavaScriptHighlighterPlugin::getLanguageName() const +{ + return QStringLiteral("QtScript"); +} + +QSyntaxHighlighter* JavaScriptHighlighterPlugin::createSyntaxHighlighter(QWidget* textEdit) const +{ + QPlainTextEdit* plainEdit = dynamic_cast(textEdit); + if (plainEdit) + return new JavaScriptSyntaxHighlighter(plainEdit->document()); + + QTextEdit* edit = dynamic_cast(textEdit); + if (edit) + return new JavaScriptSyntaxHighlighter(edit->document()); + + return nullptr; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h b/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h new file mode 100644 index 0000000..bf978d2 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/qtscriptsyntaxhighlighter.h @@ -0,0 +1,75 @@ +#ifndef JAVASCRIPTSYNTAXHIGHLIGHTER_H +#define JAVASCRIPTSYNTAXHIGHLIGHTER_H + +/* + This file is part of the Ofi Labs X2 project. + + Copyright (C) 2010 Ariya Hidayat + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "guiSQLiteStudio_global.h" +#include "plugins/builtinplugin.h" +#include "syntaxhighlighterplugin.h" +#include +#include + +/** + * @brief The JavaScript (also QtScript) highlighter + * + * This class is mostly copied from Ofi Labs X2 project. It has been slightly modified for SQLiteStudio needs. + * See the source code for the full license disclaimer. + */ +class GUI_API_EXPORT JavaScriptSyntaxHighlighter : public QSyntaxHighlighter +{ + public: + explicit JavaScriptSyntaxHighlighter(QTextDocument *parent = 0); + void mark(const QString &str, Qt::CaseSensitivity caseSensitivity); + + protected: + void highlightBlock(const QString &text); + + private: + QSet m_keywords; + QSet m_knownIds; + QString m_markString; + Qt::CaseSensitivity m_markCaseSensitivity; + QTextCharFormat keywordsFormat; +}; + +class GUI_API_EXPORT JavaScriptHighlighterPlugin : public BuiltInPlugin, public SyntaxHighlighterPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("QtScript highlighter") + SQLITESTUDIO_PLUGIN_DESC("QtScript (JavaScript) syntax highlighter.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + QString getLanguageName() const; + QSyntaxHighlighter* createSyntaxHighlighter(QWidget* textEdit) const; +}; +#endif // JAVASCRIPTSYNTAXHIGHLIGHTER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp b/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp new file mode 100644 index 0000000..ebdf2c0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.cpp @@ -0,0 +1,204 @@ +#include "searchtextlocator.h" +#include "common/unused.h" +#include +#include +#include +#include + +SearchTextLocator::SearchTextLocator(QTextDocument* document, QObject* parent) : + QObject(parent), document(document) +{ +} + +QString SearchTextLocator::getLookupString() const +{ + return lookupString; +} + +void SearchTextLocator::setLookupString(const QString& value) +{ + lookupString = value; + lastMatchStart = -1; + lastMatchEnd = -1; +} + +QString SearchTextLocator::getReplaceString() const +{ + return replaceString; +} + +void SearchTextLocator::setReplaceString(const QString& value) +{ + replaceString = value; +} + +bool SearchTextLocator::getCaseSensitive() const +{ + return caseSensitive; +} + +void SearchTextLocator::setCaseSensitive(bool value) +{ + caseSensitive = value; +} + +bool SearchTextLocator::getRegularExpression() const +{ + return regularExpression; +} + +void SearchTextLocator::setRegularExpression(bool value) +{ + regularExpression = value; +} + +bool SearchTextLocator::getSearchBackwards() const +{ + return searchBackwards; +} + +void SearchTextLocator::setSearchBackwards(bool value) +{ + searchBackwards = value; +} + +int SearchTextLocator::getStartPosition() const +{ + return startPosition; +} + +void SearchTextLocator::setStartPosition(int value) +{ + startPosition = value; + initialStartPosition = value; + afterDocPositionSwitch = false; + lastMatchStart = -1; + lastMatchEnd = -1; + emit replaceAvailable(false); +} + +QTextDocument::FindFlags SearchTextLocator::getFlags() +{ + QTextDocument::FindFlags flags; + if (caseSensitive) + flags |= QTextDocument::FindCaseSensitively; + + if (searchBackwards) + flags |= QTextDocument::FindBackward; + + return flags; +} + +void SearchTextLocator::notFound() +{ + startPosition = initialStartPosition; + afterDocPositionSwitch = false; + emit reachedEnd(); + emit replaceAvailable(false); +} + +QTextCursor SearchTextLocator::findInWholeDoc(QTextDocument::FindFlags flags) +{ + // Simply find a match + QTextCursor cursor; + if (regularExpression) + cursor = document->find(QRegExp(lookupString), startPosition, flags); + else + cursor = document->find(lookupString, startPosition, flags); + + // If not matched, see if we can find match at the other part of document (before/after init start position) + if (cursor.isNull() && !afterDocPositionSwitch) + { + afterDocPositionSwitch = true; + int start = 0; + if (flags.testFlag(QTextDocument::FindBackward)) + start = document->lastBlock().position() + document->lastBlock().length(); + + if (regularExpression) + cursor = document->find(QRegExp(lookupString), start, flags); + else + cursor = document->find(lookupString, start, flags); + } + + // If we found a match after/before start position, but it's already before/after start position, we cannot report it as a match. + if ((afterDocPositionSwitch && !cursor.isNull()) && // we have a match after switching doc position + ((!flags.testFlag(QTextDocument::FindBackward) && cursor.selectionStart() >= initialStartPosition) || // it's after init pos + (flags.testFlag(QTextDocument::FindBackward) && cursor.selectionEnd() <= initialStartPosition))) // it's before init pos + { + cursor = QTextCursor(); // exceeded search range + } + + // If we do have a match, we need to remember its parameters for the next lookup. + if (!cursor.isNull()) + { + if (flags.testFlag(QTextDocument::FindBackward)) + startPosition = cursor.selectionStart(); + else + startPosition = cursor.selectionEnd(); + + lastMatchStart = cursor.selectionStart(); + lastMatchEnd = cursor.selectionEnd(); + } + + return cursor; +} + +void SearchTextLocator::replaceCurrent() +{ + if (lastMatchStart == -1 || lastMatchEnd == -1) + return; + + QTextCursor cursor(document); + cursor.setPosition(lastMatchStart); + cursor.setPosition(lastMatchEnd, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + cursor.insertText(replaceString); +} + +bool SearchTextLocator::find(QTextDocument::FindFlags flags) +{ + if (flags == 0) + flags = getFlags(); + + QTextCursor cursor = findInWholeDoc(flags); + if (cursor.isNull()) + { + notFound(); + return false; + } + + emit found(cursor.selectionStart(), cursor.selectionEnd()); + emit replaceAvailable(true); + return true; +} + +void SearchTextLocator::findNext() +{ + QTextDocument::FindFlags flags = getFlags(); + flags &= !QTextDocument::FindBackward; + find(flags); +} + +void SearchTextLocator::findPrev() +{ + QTextDocument::FindFlags flags = getFlags(); + flags |= QTextDocument::FindBackward; + find(flags); +} + +bool SearchTextLocator::replaceAndFind() +{ + replaceCurrent(); + return find(); +} + +void SearchTextLocator::replaceAll() +{ + while (replaceAndFind()) + continue; +} + +void SearchTextLocator::cursorMoved() +{ + emit replaceAvailable(false); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h b/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h new file mode 100644 index 0000000..0cfdb44 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/searchtextlocator.h @@ -0,0 +1,75 @@ +#ifndef SEARCHTEXTLOCATOR_H +#define SEARCHTEXTLOCATOR_H + +#include "guiSQLiteStudio_global.h" +#include +#include + +class GUI_API_EXPORT SearchTextLocator : public QObject +{ + Q_OBJECT + + public: + struct GUI_API_EXPORT Occurrance + { + int start; + int end; + }; + + SearchTextLocator(QTextDocument* document, QObject* parent = nullptr); + + QString getLookupString() const; + void setLookupString(const QString& value); + + QString getReplaceString() const; + void setReplaceString(const QString& value); + + bool getCaseSensitive() const; + void setCaseSensitive(bool value); + + bool getRegularExpression() const; + void setRegularExpression(bool value); + + bool getSearchBackwards() const; + void setSearchBackwards(bool value); + + int getStartPosition() const; + void setStartPosition(int value); + + void reset(); + + private: + QTextDocument::FindFlags getFlags(); + void notFound(); + QTextCursor findInWholeDoc(QTextDocument::FindFlags flags); + void replaceCurrent(); + + QTextDocument* document = nullptr; + int initialStartPosition; + int lastMatchStart = -1; + int lastMatchEnd = -1; + bool afterDocPositionSwitch = false; + + // Config parameters + QString lookupString; + QString replaceString; + bool caseSensitive = false; + bool regularExpression = false; + bool searchBackwards = false; + int startPosition = 0; + + public slots: + bool find(QTextDocument::FindFlags flags = 0); + void findNext(); + void findPrev(); + bool replaceAndFind(); + void replaceAll(); + void cursorMoved(); + + signals: + void found(int start, int end); + void reachedEnd(); + void replaceAvailable(bool available); +}; + +#endif // SEARCHTEXTLOCATOR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp new file mode 100644 index 0000000..f19d52d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.cpp @@ -0,0 +1,109 @@ +#include "selectabledbmodel.h" +#include "dbtree/dbtreeitem.h" +#include "dbtree/dbtreemodel.h" + +SelectableDbModel::SelectableDbModel(QObject* parent) : + QSortFilterProxyModel(parent) +{ +} + +QVariant SelectableDbModel::data(const QModelIndex& index, int role) const +{ + if (role != Qt::CheckStateRole) + return QSortFilterProxyModel::data(index, role); + + DbTreeItem* item = getItemForProxyIndex(index); + if (!item) + return QSortFilterProxyModel::data(index, role); + + DbTreeItem::Type type = item->getType(); + if (type != DbTreeItem::Type::DB) + return QSortFilterProxyModel::data(index, role); + + return checkedDatabases.contains(item->text(), Qt::CaseInsensitive) ? Qt::Checked : Qt::Unchecked; +} + +bool SelectableDbModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role != Qt::CheckStateRole) + return QSortFilterProxyModel::setData(index, value, role); + + DbTreeItem* item = getItemForProxyIndex(index); + if (!item) + return QSortFilterProxyModel::setData(index, value, role); + + DbTreeItem::Type type = item->getType(); + if (type != DbTreeItem::Type::DB) + return QSortFilterProxyModel::setData(index, value, role); + + if (value.toBool()) + checkedDatabases << item->text(); + else + checkedDatabases.removeOne(item->text()); + + emit dataChanged(index, index, {Qt::CheckStateRole}); + + return true; +} + +Qt::ItemFlags SelectableDbModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags itemFlags = QSortFilterProxyModel::flags(index); + + DbTreeItem* item = getItemForProxyIndex(index); + if (!item) + return itemFlags; + + DbTreeItem::Type type = item->getType(); + if (item->getDb() && item->getDb()->getVersion() == disabledVersion) + itemFlags ^= Qt::ItemIsEnabled; + else if (type == DbTreeItem::Type::DB) + itemFlags |= Qt::ItemIsUserCheckable; + + return itemFlags; +} + +void SelectableDbModel::setDatabases(const QStringList& databases) +{ + beginResetModel(); + checkedDatabases = databases; + endResetModel(); +} + +QStringList SelectableDbModel::getDatabases() const +{ + return checkedDatabases; +} + +bool SelectableDbModel::filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const +{ + QModelIndex idx = sourceModel()->index(srcRow, 0, srcParent); + DbTreeItem* item = dynamic_cast(dynamic_cast(sourceModel())->itemFromIndex(idx)); + switch (item->getType()) + { + case DbTreeItem::Type::DIR: + case DbTreeItem::Type::DB: + return true; + default: + return false; + } + return false; +} + +DbTreeItem* SelectableDbModel::getItemForProxyIndex(const QModelIndex& index) const +{ + QModelIndex srcIdx = mapToSource(index); + DbTreeItem* item = dynamic_cast(dynamic_cast(sourceModel())->itemFromIndex(srcIdx)); + return item; +} +int SelectableDbModel::getDisabledVersion() const +{ + return disabledVersion; +} + +void SelectableDbModel::setDisabledVersion(int value) +{ + disabledVersion = value; +} + + diff --git a/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h b/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h new file mode 100644 index 0000000..bbae582 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/selectabledbmodel.h @@ -0,0 +1,36 @@ +#ifndef SELECTABLEDBMODEL_H +#define SELECTABLEDBMODEL_H + +#include "guiSQLiteStudio_global.h" +#include +#include +#include + +class DbTreeItem; + +class GUI_API_EXPORT SelectableDbModel : public QSortFilterProxyModel +{ + public: + explicit SelectableDbModel(QObject *parent = 0); + + QVariant data(const QModelIndex& index, int role) const; + bool setData(const QModelIndex& index, const QVariant& value, int role); + Qt::ItemFlags flags(const QModelIndex& index) const; + + void setDatabases(const QStringList& databases); + QStringList getDatabases() const; + + int getDisabledVersion() const; + void setDisabledVersion(int value); + + protected: + bool filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const; + + private: + DbTreeItem* getItemForProxyIndex(const QModelIndex& index) const; + + QStringList checkedDatabases; + int disabledVersion = -1; +}; + +#endif // SELECTABLEDBMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp new file mode 100644 index 0000000..75579d5 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.cpp @@ -0,0 +1,244 @@ +#include "selectabledbobjmodel.h" +#include "dbtree/dbtreeitem.h" +#include "dbtree/dbtreemodel.h" +#include +#include + +SelectableDbObjModel::SelectableDbObjModel(QObject *parent) : + QSortFilterProxyModel(parent) +{ +} + +QVariant SelectableDbObjModel::data(const QModelIndex& index, int role) const +{ + if (role != Qt::CheckStateRole) + return QSortFilterProxyModel::data(index, role); + + return getStateFromChilds(index); +} + +bool SelectableDbObjModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role != Qt::CheckStateRole) + return QSortFilterProxyModel::setData(index, value, role); + + Qt::CheckState checked = static_cast(value.toInt()); + setRecurrently(index, checked); + emit dataChanged(index, index, {Qt::CheckStateRole}); + + return true; +} + +Qt::ItemFlags SelectableDbObjModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = QSortFilterProxyModel::flags(index); + DbTreeItem* item = getItemForProxyIndex(index); + switch (item->getType()) + { + case DbTreeItem::Type::TABLE: + case DbTreeItem::Type::VIRTUAL_TABLE: + case DbTreeItem::Type::INDEX: + case DbTreeItem::Type::TRIGGER: + case DbTreeItem::Type::VIEW: + case DbTreeItem::Type::DB: + case DbTreeItem::Type::TABLES: + case DbTreeItem::Type::INDEXES: + case DbTreeItem::Type::TRIGGERS: + case DbTreeItem::Type::VIEWS: + { + flags |= Qt::ItemIsUserCheckable; + if (index.child(0, 0).isValid()) + flags |= Qt::ItemIsTristate; + + break; + } + case DbTreeItem::Type::DIR: + case DbTreeItem::Type::COLUMNS: + case DbTreeItem::Type::COLUMN: + case DbTreeItem::Type::ITEM_PROTOTYPE: + break; + } + + return flags; +} + +QString SelectableDbObjModel::getDbName() const +{ + return dbName; +} + +void SelectableDbObjModel::setDbName(const QString& value) +{ + beginResetModel(); + dbName = value; + checkedObjects.clear(); + endResetModel(); +} +QStringList SelectableDbObjModel::getCheckedObjects() const +{ + return checkedObjects.toList(); +} + +void SelectableDbObjModel::setCheckedObjects(const QStringList& value) +{ + checkedObjects = value.toSet(); +} + +void SelectableDbObjModel::setRootChecked(bool checked) +{ + QModelIndex idx = index(0, 0); + if (!idx.isValid()) + return; + + setData(idx, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); +} + +DbTreeItem* SelectableDbObjModel::getItemForIndex(const QModelIndex& index) const +{ + return getItemForProxyIndex(index); +} + +bool SelectableDbObjModel::filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const +{ + QModelIndex idx = sourceModel()->index(srcRow, 0, srcParent); + DbTreeItem* item = dynamic_cast(dynamic_cast(sourceModel())->itemFromIndex(idx)); + DbTreeItem* dbItem = item->getPathToParentItem(DbTreeItem::Type::DB).last(); + + // These 3 conditions could be written as one OR-ed, but this is easier to debug which one fails this way. + if (!dbItem) + return false; + + if (item->getType() == DbTreeItem::Type::DIR) + return checkRecurrentlyForDb(item); + + if (!dbItem->getDb()) + return false; + + if (dbItem->getDb()->getName() != dbName) + return false; + + switch (item->getType()) + { + case DbTreeItem::Type::TABLE: + case DbTreeItem::Type::VIRTUAL_TABLE: + case DbTreeItem::Type::INDEX: + case DbTreeItem::Type::TRIGGER: + case DbTreeItem::Type::VIEW: + case DbTreeItem::Type::DB: + return true; + case DbTreeItem::Type::TABLES: + case DbTreeItem::Type::INDEXES: + case DbTreeItem::Type::TRIGGERS: + case DbTreeItem::Type::VIEWS: + return item->rowCount() > 0; + case DbTreeItem::Type::DIR: + case DbTreeItem::Type::COLUMNS: + case DbTreeItem::Type::COLUMN: + case DbTreeItem::Type::ITEM_PROTOTYPE: + return false; + } + return false; +} + +DbTreeItem* SelectableDbObjModel::getItemForProxyIndex(const QModelIndex& index) const +{ + QModelIndex srcIdx = mapToSource(index); + DbTreeItem* item = dynamic_cast(dynamic_cast(sourceModel())->itemFromIndex(srcIdx)); + return item; +} + +Qt::CheckState SelectableDbObjModel::getStateFromChilds(const QModelIndex& index) const +{ + DbTreeItem* item = getItemForProxyIndex(index); + if (!item) + return Qt::Unchecked; + + if (!index.child(0, 0).isValid()) + { + if (isObject(item) && checkedObjects.contains(item->text())) + return Qt::Checked; + else + return Qt::Unchecked; + } + + int total = 0; + int checked = 0; + int partial = 0; + Qt::CheckState state; + QModelIndex child; + for (int i = 0; (child = index.child(i, 0)).isValid(); i++) + { + if (!child.flags().testFlag(Qt::ItemIsUserCheckable)) + continue; + + total++; + state = static_cast(child.data(Qt::CheckStateRole).toInt()); + if (state == Qt::Checked || state == Qt::PartiallyChecked) + { + checked++; + if (state == Qt::PartiallyChecked) + partial++; + } + } + + if (total == checked) + { + if (partial > 0) + return Qt::PartiallyChecked; + else + return Qt::Checked; + } + + if (checked == 0) + { + if (isObject(item) && checkedObjects.contains(item->text())) + return Qt::PartiallyChecked; + else + return Qt::Unchecked; + } + + return Qt::PartiallyChecked; +} + +void SelectableDbObjModel::setRecurrently(const QModelIndex& index, Qt::CheckState checked) +{ + DbTreeItem* item = getItemForProxyIndex(index); + if (!item) + return; + + if (checked && isObject(item)) + checkedObjects << item->text(); + else + checkedObjects.remove(item->text()); + + if (!index.child(0, 0).isValid()) + return; + + // Limiting checked to 'checked/unchecked', cause recurrent marking cannot set partially checked, it makes no sense + checked = (checked > 0 ? Qt::Checked : Qt::Unchecked); + + QModelIndex child; + for (int i = 0; (child = index.child(i, 0)).isValid(); i++) + setData(child, checked, Qt::CheckStateRole); +} + +bool SelectableDbObjModel::isObject(DbTreeItem* item) const +{ + switch (item->getType()) + { + case DbTreeItem::Type::TABLE: + case DbTreeItem::Type::INDEX: + case DbTreeItem::Type::TRIGGER: + case DbTreeItem::Type::VIEW: + case DbTreeItem::Type::VIRTUAL_TABLE: + return true; + default: + break; + } + return false; +} + +bool SelectableDbObjModel::checkRecurrentlyForDb(DbTreeItem* item) const +{ + return item->findItem(DbTreeItem::Type::DB, dbName) != nullptr; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h b/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h new file mode 100644 index 0000000..2fca1b4 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/selectabledbobjmodel.h @@ -0,0 +1,44 @@ +#ifndef SELECTABLEDBOBJMODEL_H +#define SELECTABLEDBOBJMODEL_H + +#include "guiSQLiteStudio_global.h" +#include +#include + +class DbTreeItem; +class QTreeView; + +class GUI_API_EXPORT SelectableDbObjModel : public QSortFilterProxyModel +{ + Q_OBJECT + public: + explicit SelectableDbObjModel(QObject *parent = 0); + + QVariant data(const QModelIndex& index, int role) const; + bool setData(const QModelIndex& index, const QVariant& value, int role); + Qt::ItemFlags flags(const QModelIndex& index) const; + + QString getDbName() const; + void setDbName(const QString& value); + + QStringList getCheckedObjects() const; + void setCheckedObjects(const QStringList& value); + + void setRootChecked(bool checked); + DbTreeItem* getItemForIndex(const QModelIndex& index) const; + + protected: + bool filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const; + + private: + DbTreeItem* getItemForProxyIndex(const QModelIndex& index) const; + Qt::CheckState getStateFromChilds(const QModelIndex& index) const; + void setRecurrently(const QModelIndex& index, Qt::CheckState checked); + bool isObject(DbTreeItem* item) const; + bool checkRecurrentlyForDb(DbTreeItem* item) const; + + QSet checkedObjects; + QString dbName; +}; + +#endif // SELECTABLEDBOBJMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp b/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp new file mode 100644 index 0000000..0b5b91d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.cpp @@ -0,0 +1,143 @@ +#include "sqlcompareview.h" +#include "sqlview.h" +#include "common/utils.h" +#include "diff/diff_match_patch.h" +#include "sqlitesyntaxhighlighter.h" +#include +#include + +SqlCompareView::SqlCompareView(QWidget *parent) : + QTableWidget(parent) +{ + setColumnCount(2); + setVerticalScrollMode(ScrollPerPixel); + horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + horizontalHeader()->setVisible(false); +// verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + diff = new diff_match_patch; +} + +void SqlCompareView::setSides(const QList>& data) +{ + setRowCount(data.size()); + + int row = 0; + SqlView* leftView = nullptr; + SqlView* rightView = nullptr; + for (const QPair& rowData : data) + { + leftView = new SqlView(); + leftView->setFrameStyle(QFrame::NoFrame); + leftView->setPlainText(rowData.first); + setCellWidget(row, 0, leftView); + + rightView = new SqlView(); + rightView->setFrameStyle(QFrame::NoFrame); + rightView->setPlainText(rowData.second); + setCellWidget(row, 1, rightView); + + setupHighlighting(rowData.first, rowData.second, leftView, rightView); + + row++; + } + updateLabels(); + updateSizes(); +} + +void SqlCompareView::setLeftLabel(const QString& label) +{ + leftLabel = label; +} + +void SqlCompareView::setRightLabel(const QString& label) +{ + rightLabel = label; +} + +void SqlCompareView::updateSizes() +{ + if (rowCount() == 0 || !isVisible()) + return; + + SqlView* view = dynamic_cast(cellWidget(0, 0)); + if (!view) + { + qCritical() << "Not a SqlView in SqlCompareView::updateSizes():" << cellWidget(0, 0); + return; + } + + QFont font = view->font(); + QFontMetrics fm(font); + + int leftWidth = horizontalHeader()->sectionSize(0); + int rightWidth = horizontalHeader()->sectionSize(1); + + SqlView* leftView = nullptr; + SqlView* rightView = nullptr; + QSize leftSize; + QSize rightSize; + for (int row = 0, total = rowCount(); row < total; ++row) + { + leftView = dynamic_cast(cellWidget(row, 0)); + leftView->document()->setTextWidth(leftWidth); + + rightView = dynamic_cast(cellWidget(row, 1)); + rightView->document()->setTextWidth(rightWidth); + + leftSize = QSize(leftWidth, leftView->document()->size().toSize().height()); + rightSize = QSize(rightWidth, rightView->document()->size().toSize().height()); + if (leftSize.height() > rightSize.height()) + rightSize.setHeight(leftSize.height()); + else + leftSize.setHeight(rightSize.height()); + + leftView->setFixedSize(leftSize); + rightView->setFixedSize(rightSize); + } + verticalHeader()->resizeSections(QHeaderView::ResizeToContents); +} + +void SqlCompareView::updateLabels() +{ + setHorizontalHeaderLabels({leftLabel, rightLabel}); +} + +void SqlCompareView::setupHighlighting(const QString& left, const QString& right, SqlView* leftView, SqlView* rightView) +{ + QList diffs = diff->diff_main(left, right); + int leftPos = 0; + int rightPos = 0; + int lgt = 0; + for (const Diff& d : diffs) + { + lgt = d.text.length(); + switch (d.operation) + { + case DELETE: + leftView->setTextBackgroundColor(leftPos, leftPos + lgt - 1, Qt::red); + leftPos += lgt; + break; + case EQUAL: + leftPos += lgt; + rightPos += lgt; + break; + case INSERT: + rightView->setTextBackgroundColor(leftPos, leftPos + lgt - 1, Qt::green); + rightPos += lgt; + break; + } + } +} + +void SqlCompareView::resizeEvent(QResizeEvent* e) +{ + QTableWidget::resizeEvent(e); + updateSizes(); +} + +void SqlCompareView::showEvent(QShowEvent* e) +{ + QTableWidget::showEvent(e); + updateSizes(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h b/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h new file mode 100644 index 0000000..175e35a --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/sqlcompareview.h @@ -0,0 +1,34 @@ +#ifndef SQLCOMPAREVIEW_H +#define SQLCOMPAREVIEW_H + +#include "guiSQLiteStudio_global.h" +#include + +class SqlView; +class diff_match_patch; +class SqliteSyntaxHighlighter; + +class GUI_API_EXPORT SqlCompareView : public QTableWidget +{ + public: + explicit SqlCompareView(QWidget* parent = 0); + + void setSides(const QList>& data); + void setLeftLabel(const QString& label); + void setRightLabel(const QString& label); + void updateSizes(); + + protected: + void resizeEvent(QResizeEvent* e); + void showEvent(QShowEvent* e); + + private: + void updateLabels(); + void setupHighlighting(const QString& left, const QString& right, SqlView* leftView, SqlView* rightView); + + QString leftLabel; + QString rightLabel; + diff_match_patch* diff = nullptr; +}; + +#endif // SQLCOMPAREVIEW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp b/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp new file mode 100644 index 0000000..52e1676 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp @@ -0,0 +1,1521 @@ +#include "sqleditor.h" +#include "uiconfig.h" +#include "uiutils.h" +#include "services/config.h" +#include "iconmanager.h" +#include "completer/completerwindow.h" +#include "completionhelper.h" +#include "common/utils_sql.h" +#include "parser/lexer.h" +#include "parser/parser.h" +#include "parser/parsererror.h" +#include "common/unused.h" +#include "services/notifymanager.h" +#include "dialogs/searchtextdialog.h" +#include "dbobjectdialogs.h" +#include "searchtextlocator.h" +#include "services/codeformatter.h" +#include "sqlitestudio.h" +#include "dbtree/dbtreeitem.h" +#include "dbtree/dbtree.h" +#include "dbtree/dbtreemodel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(SqlEditor) + +SqlEditor::SqlEditor(QWidget *parent) : + QPlainTextEdit(parent) +{ + init(); +} + +SqlEditor::~SqlEditor() +{ + if (queryParser) + { + delete queryParser; + queryParser = nullptr; + } +} + +void SqlEditor::init() +{ + highlighter = new SqliteSyntaxHighlighter(document()); + setFont(CFG_UI.Fonts.SqlEditor.get()); + initActions(); + setupMenu(); + + textLocator = new SearchTextLocator(document(), this); + connect(textLocator, SIGNAL(found(int,int)), this, SLOT(found(int,int))); + connect(textLocator, SIGNAL(reachedEnd()), this, SLOT(reachedEnd())); + + lineNumberArea = new LineNumberArea(this); + + connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth())); + connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorMoved())); + + updateLineNumberAreaWidth(); + highlightCurrentLine(); + + completer = new CompleterWindow(this); + connect(completer, SIGNAL(accepted()), this, SLOT(completeSelected())); + connect(completer, SIGNAL(textTyped(QString)), this, SLOT(completerTypedText(QString))); + connect(completer, SIGNAL(backspacePressed()), this, SLOT(completerBackspacePressed())); + connect(completer, SIGNAL(leftPressed()), this, SLOT(completerLeftPressed())); + connect(completer, SIGNAL(rightPressed()), this, SLOT(completerRightPressed())); + + autoCompleteTimer = new QTimer(this); + autoCompleteTimer->setSingleShot(true); + autoCompleteTimer->setInterval(autoCompleterDelay); + connect(autoCompleteTimer, SIGNAL(timeout()), this, SLOT(checkForAutoCompletion())); + + queryParserTimer = new QTimer(this); + queryParserTimer->setSingleShot(true); + queryParserTimer->setInterval(queryParserDelay); + connect(queryParserTimer, SIGNAL(timeout()), this, SLOT(parseContents())); + connect(this, SIGNAL(textChanged()), this, SLOT(scheduleQueryParser())); + + queryParser = new Parser(Dialect::Sqlite3); + + connect(this, &QWidget::customContextMenuRequested, this, &SqlEditor::customContextMenuRequested); + connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant))); + connect(CFG, SIGNAL(massSaveCommited()), this, SLOT(configModified())); +} + +void SqlEditor::removeErrorMarkers() +{ + highlighter->clearErrors(); +} + +bool SqlEditor::haveErrors() +{ + return highlighter->haveErrors(); +} + +bool SqlEditor::isSyntaxChecked() +{ + return syntaxValidated; +} + +void SqlEditor::markErrorAt(int start, int end, bool limitedDamage) +{ + highlighter->addError(start, end, limitedDamage); +} + +void SqlEditor::createActions() +{ + createAction(CUT, ICONS.ACT_CUT, tr("Cut", "sql editor"), this, SLOT(cut()), this); + createAction(COPY, ICONS.ACT_COPY, tr("Copy", "sql editor"), this, SLOT(copy()), this); + createAction(PASTE, ICONS.ACT_PASTE, tr("Paste", "sql editor"), this, SLOT(paste()), this); + createAction(DELETE, ICONS.ACT_DELETE, tr("Delete", "sql editor"), this, SLOT(deleteSelected()), this); + createAction(SELECT_ALL, ICONS.ACT_SELECT_ALL, tr("Select all", "sql editor"), this, SLOT(selectAll()), this); + createAction(UNDO, ICONS.ACT_UNDO, tr("Undo", "sql editor"), this, SLOT(undo()), this); + createAction(REDO, ICONS.ACT_REDO, tr("Redo", "sql editor"), this, SLOT(redo()), this); + createAction(COMPLETE, ICONS.COMPLETE, tr("Complete", "sql editor"), this, SLOT(complete()), this); + createAction(FORMAT_SQL, ICONS.FORMAT_SQL, tr("Format SQL", "sql editor"), this, SLOT(formatSql()), this); + createAction(SAVE_SQL_FILE, ICONS.SAVE_SQL_FILE, tr("Save SQL to file", "sql editor"), this, SLOT(saveToFile()), this); + createAction(OPEN_SQL_FILE, ICONS.OPEN_SQL_FILE, tr("Load SQL from file", "sql editor"), this, SLOT(loadFromFile()), this); + createAction(DELETE_LINE, ICONS.ACT_DEL_LINE, tr("Delete line", "sql editor"), this, SLOT(deleteLine()), this); + createAction(MOVE_BLOCK_DOWN, tr("Move block down", "sql editor"), this, SLOT(moveBlockDown()), this); + createAction(MOVE_BLOCK_UP, tr("Move block up", "sql editor"), this, SLOT(moveBlockUp()), this); + createAction(COPY_BLOCK_DOWN, tr("Copy block down", "sql editor"), this, SLOT(copyBlockDown()), this); + createAction(COPY_BLOCK_UP, tr("Copy up down", "sql editor"), this, SLOT(copyBlockUp()), this); + createAction(FIND, ICONS.ACT_SEARCH, tr("Find", "sql editor"), this, SLOT(find()), this); + createAction(FIND_NEXT, tr("Find next", "sql editor"), this, SLOT(findNext()), this); + createAction(FIND_PREV, tr("Find previous", "sql editor"), this, SLOT(findPrevious()), this); + createAction(REPLACE, tr("Replace", "sql editor"), this, SLOT(replace()), this); + + actionMap[CUT]->setEnabled(false); + actionMap[COPY]->setEnabled(false); + actionMap[UNDO]->setEnabled(false); + actionMap[REDO]->setEnabled(false); + actionMap[DELETE]->setEnabled(false); + + connect(this, &QPlainTextEdit::undoAvailable, this, &SqlEditor::updateUndoAction); + connect(this, &QPlainTextEdit::redoAvailable, this, &SqlEditor::updateRedoAction); + connect(this, &QPlainTextEdit::copyAvailable, this, &SqlEditor::updateCopyAction); +} + +void SqlEditor::setupDefShortcuts() +{ + setShortcutContext({CUT, COPY, PASTE, DELETE, SELECT_ALL, UNDO, REDO, COMPLETE, FORMAT_SQL, SAVE_SQL_FILE, OPEN_SQL_FILE, + DELETE_LINE}, Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(SqlEditor, Action); +} + +void SqlEditor::setupMenu() +{ + contextMenu = new QMenu(this); + contextMenu->addAction(actionMap[FORMAT_SQL]); + contextMenu->addSeparator(); + contextMenu->addAction(actionMap[SAVE_SQL_FILE]); + contextMenu->addAction(actionMap[OPEN_SQL_FILE]); + contextMenu->addSeparator(); + contextMenu->addAction(actionMap[UNDO]); + contextMenu->addAction(actionMap[REDO]); + contextMenu->addSeparator(); + contextMenu->addAction(actionMap[FIND]); + contextMenu->addAction(actionMap[CUT]); + contextMenu->addAction(actionMap[COPY]); + contextMenu->addAction(actionMap[PASTE]); + contextMenu->addAction(actionMap[DELETE]); + contextMenu->addSeparator(); + contextMenu->addAction(actionMap[SELECT_ALL]); + + validObjContextMenu = new QMenu(this); +} + +Db* SqlEditor::getDb() const +{ + return db; +} + +void SqlEditor::setDb(Db* value) +{ + db = value; + scheduleQueryParser(true); +} + +void SqlEditor::setAutoCompletion(bool enabled) +{ + autoCompletion = enabled; +} + +void SqlEditor::customContextMenuRequested(const QPoint &pos) +{ + if (objectLinksEnabled) + { + const DbObject* obj = getValidObjectForPosition(pos); + QString objName = toPlainText().mid(obj->from, (obj->to - obj->from + 1)); + + validObjContextMenu->clear(); + + DbTreeItem* item = nullptr; + for (DbTreeItem::Type type : {DbTreeItem::Type::TABLE, DbTreeItem::Type::INDEX, DbTreeItem::Type::TRIGGER, DbTreeItem::Type::VIEW}) + { + item = DBTREE->getModel()->findItem(type, objName); + if (item) + break; + } + + if (item) + { + DBTREE->setSelectedItem(item); + DBTREE->setupActionsForMenu(item, validObjContextMenu); + if (validObjContextMenu->actions().size() == 0) + return; + + DBTREE->updateActionStates(item); + validObjContextMenu->popup(mapToGlobal(pos)); + } + return; + } + contextMenu->popup(mapToGlobal(pos)); +} + +void SqlEditor::updateUndoAction(bool enabled) +{ + actionMap[UNDO]->setEnabled(enabled); +} + +void SqlEditor::updateRedoAction(bool enabled) +{ + actionMap[REDO]->setEnabled(enabled); +} + +void SqlEditor::updateCopyAction(bool enabled) +{ + actionMap[COPY]->setEnabled(enabled); + actionMap[CUT]->setEnabled(enabled); + actionMap[DELETE]->setEnabled(enabled); +} + +void SqlEditor::deleteSelected() +{ + textCursor().removeSelectedText(); +} + +void SqlEditor::homePressed(Qt::KeyboardModifiers modifiers) +{ + QTextCursor cursor = textCursor(); + + bool shift = modifiers.testFlag(Qt::ShiftModifier); + QTextCursor::MoveMode mode = shift ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor; + if (modifiers.testFlag(Qt::ControlModifier)) + { + cursor.setPosition(0, mode); + setTextCursor(cursor); + return; + } + + int curPos = cursor.positionInBlock(); + QString line = cursor.block().text(); + int firstPrintable = line.indexOf(QRegExp("\\S")); + + if (firstPrintable <= 0) + { + // If first printable character is the first character in line, + // or there's no printable characters at all, move to start of line. + cursor.movePosition(QTextCursor::StartOfLine, mode); + } + else if (curPos == 0 || curPos < firstPrintable) + { + // If cursor is at the line begining, or it's still before first printable character. + // Move to first printable character. + cursor.movePosition(QTextCursor::NextWord, mode); + } + else if (curPos == firstPrintable) + { + // If cursor is already at first printable character, now is the time to move it + // to start of the line. + cursor.movePosition(QTextCursor::StartOfLine, mode); + } + else + { + // Cursor is somewhere in the middle of printable text. Move it to the begining + // of line and then to first printable character. + cursor.movePosition(QTextCursor::StartOfLine, mode); + cursor.movePosition(QTextCursor::NextWord, mode); + } + + setTextCursor(cursor); +} + +void SqlEditor::tabPressed(bool shiftPressed) +{ + QTextCursor cursor = textCursor(); + if (cursor.hasSelection()) + { + indentSelected(shiftPressed); + return; + } + + // Get current line, its first printable character + int curPos = cursor.positionInBlock(); + QString line = cursor.block().text(); + int firstPrintable = line.indexOf(QRegExp("\\S")); + + // Handle shift+tab (unindent) + if (shiftPressed) + { + cursor.movePosition(QTextCursor::StartOfLine); + + if (firstPrintable > 0) + cursor.movePosition(QTextCursor::NextWord); + + setTextCursor(cursor); + backspacePressed(); + return; + } + + // If we're past any printable character (and there was any), insert a tab + if (curPos > firstPrintable && firstPrintable >= 0) + { + insertPlainText(" "); + return; + } + + // If there is no previous block to refer to, insert a tab + QTextBlock previousBlock = document()->findBlockByNumber(cursor.blockNumber() - 1); + if (!previousBlock.isValid()) + { + insertPlainText(" "); + return; + } + + // If previous block has first pritable character further than current cursor position, insert spaces to meet above position + int previousFirstPrintable = previousBlock.text().indexOf(QRegExp("\\S")); + if (curPos < previousFirstPrintable) + { + insertPlainText(QString(" ").repeated(previousFirstPrintable - curPos)); + return; + } + + // At this point we know that previous block don't have first printable character further than the cursor. Insert tab. + insertPlainText(" "); +} + +void SqlEditor::backspacePressed() +{ + // If we have any selection, delete it and that's all. + QTextCursor cursor = textCursor(); + if (cursor.hasSelection()) + { + deleteSelected(); + return; + } + + // No selection. Collect line, cursor position, first and last printable characters in line. + int curPos = cursor.positionInBlock(); + QString line = cursor.block().text(); + int firstPrintable = line.indexOf(QRegExp("\\S")); + + // If there is any printable character (which means that line length is greater than 0) and cursor is after first character, + // or when cursor is at the begining of line, delete previous character, always. + if ((firstPrintable > -1 && curPos > firstPrintable) || curPos == 0) + { + cursor.deletePreviousChar(); + return; + } + + // Define number of spaces available for deletion. + int spaces = firstPrintable; + if (spaces < 0) + spaces = curPos; + + // Get previous block. If there was none, then delete up to 4 previous spaces. + QTextBlock previousBlock = document()->findBlockByNumber(cursor.blockNumber() - 1); + if (!previousBlock.isValid()) + { + doBackspace(spaces < 4 ? spaces : 4); + return; + } + + // If first printable character in previous block is prior to the current cursor position (but not first in the line), + // delete as many spaces, as necessary to reach the same position, but never more than defined spaces number earlier. + int previousFirstPrintable = previousBlock.text().indexOf(QRegExp("\\S")); + if (curPos > previousFirstPrintable && previousFirstPrintable > 0) + { + int spacesToDelete = curPos - previousFirstPrintable; + doBackspace(spaces < spacesToDelete ? spaces : spacesToDelete); + return; + } + + // There is no character to back off to, so we simply delete up to 4 previous spaces. + doBackspace(spaces < 4 ? spaces : 4); +} + +void SqlEditor::complete() +{ + if (!db || !db->isValid()) + { + notifyWarn(tr("Syntax completion can be used only when a valid database is set for the SQL editor.")); + return; + } + + QString sql = toPlainText(); + int curPos = textCursor().position(); + + if (!virtualSqlExpression.isNull()) + { + sql = virtualSqlExpression.arg(sql); + curPos += virtualSqlOffset; + } + + CompletionHelper completionHelper(sql, curPos, db); + completionHelper.setCreateTriggerTable(createTriggerTable); + CompletionHelper::Results result = completionHelper.getExpectedTokens(); + if (result.filtered().size() == 0) + return; + + completer->setData(result); + completer->setDb(db); + if (completer->immediateResolution()) + return; + + updateCompleterPosition(); + completer->show(); +} + +void SqlEditor::updateCompleterPosition() +{ + QPoint pos = cursorRect().bottomRight(); + pos += QPoint(1, fontMetrics().descent()); + completer->move(mapToGlobal(pos)); +} + +void SqlEditor::completeSelected() +{ + deletePreviousChars(completer->getNumberOfCharsToRemove()); + + ExpectedTokenPtr token = completer->getSelected(); + QString value = token->value; + if (token->needsWrapping()) + value = wrapObjIfNeeded(value, getDialect()); + + if (!token->prefix.isNull()) + { + value.prepend("."); + value.prepend(wrapObjIfNeeded(token->prefix, getDialect())); + } + + insertPlainText(value); +} + +void SqlEditor::scheduleAutoCompletion() +{ + autoCompleteTimer->stop(); + + if (autoCompletion && !deletionKeyPressed) + autoCompleteTimer->start(); +} + +void SqlEditor::checkForAutoCompletion() +{ + if (!db || !autoCompletion || deletionKeyPressed) + return; + + Lexer lexer(getDialect()); + QString sql = toPlainText(); + int curPos = textCursor().position(); + TokenList tokens = lexer.tokenize(sql.left(curPos)); + + if (tokens.size() > 0 && tokens.last()->type == Token::OPERATOR && tokens.last()->value == ".") + complete(); +} + +void SqlEditor::deletePreviousChars(int length) +{ + QTextCursor cursor = textCursor(); + for (int i = 0; i < length; i++) + cursor.deletePreviousChar(); +} + +void SqlEditor::refreshValidObjects() +{ + if (!db || !db->isValid()) + return; + + objectsInNamedDb.clear(); + + SchemaResolver resolver(db); + QSet databases = resolver.getDatabases(); + databases << "main"; + QStringList objects; + foreach (const QString& dbName, databases) + { + objects = resolver.getAllObjects(); + objectsInNamedDb[dbName] << objects; + } +} + +Dialect SqlEditor::getDialect() +{ + return !db ? Dialect::Sqlite3 : db->getDialect(); +} + +void SqlEditor::setObjectLinks(bool enabled) +{ + objectLinksEnabled = enabled; + setMouseTracking(enabled); + highlighter->setObjectLinksEnabled(enabled); + highlighter->rehighlight(); + + if (enabled) + handleValidObjectCursor(mapFromGlobal(QCursor::pos())); + else + viewport()->setCursor(Qt::IBeamCursor); +} + +void SqlEditor::addDbObject(int from, int to, const QString& dbName) +{ + validDbObjects << DbObject(from, to, dbName); + highlighter->addDbObject(from, to); +} + +void SqlEditor::clearDbObjects() +{ + validDbObjects.clear(); + highlighter->clearDbObjects(); +} + +void SqlEditor::lineNumberAreaPaintEvent(QPaintEvent* event) +{ + QPainter painter(lineNumberArea); + painter.fillRect(event->rect(), CFG_UI.Colors.SqlEditorLineNumAreaBg.get()); + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int) blockBoundingRect(block).height(); + while (block.isValid() && top <= event->rect().bottom()) + { + if (block.isVisible() && bottom >= event->rect().top()) + { + QString number = QString::number(blockNumber + 1); + painter.setPen(Qt::black); + painter.drawText(0, top, lineNumberArea->width()-2, fontMetrics().height(), Qt::AlignRight, number); + } + + block = block.next(); + top = bottom; + bottom = top + (int) blockBoundingRect(block).height(); + blockNumber++; + } +} + +int SqlEditor::lineNumberAreaWidth() +{ + int digits = 1; + int max = qMax(1, document()->blockCount()); + while (max >= 10) + { + max /= 10; + digits++; + } + + int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits; + return space; +} + +void SqlEditor::highlightParenthesis() +{ + // Clear extra selections + QList selections = extraSelections(); + + // Just keep "current line" highlighting + QMutableListIterator it(selections); + while (it.hasNext()) + { + if (!it.next().format.property(QTextFormat::FullWidthSelection).toBool()) + it.remove(); + } + setExtraSelections(selections); + + // Find out parenthesis under the cursor + int curPos = textCursor().position(); + TextBlockData* data = dynamic_cast(textCursor().block().userData()); + if (!data) + return; + + const TextBlockData::Parenthesis* parOnRight = data->parenthesisForPosision(curPos); + const TextBlockData::Parenthesis* parOnLeft = data->parenthesisForPosision(curPos - 1); + const TextBlockData::Parenthesis* thePar = parOnRight; + if (parOnLeft && !parOnRight) // go with parenthesis on left only when there's no parenthesis on right + thePar = parOnLeft; + + if (!thePar) + return; + + // Getting all parenthesis in the entire document + QList allParenthesis; + for (QTextBlock block = document()->begin(); block.isValid(); block = block.next()) + { + data = dynamic_cast(block.userData()); + if (!data) + continue; + + allParenthesis += data->parentheses(); + } + + // Matching the parenthesis + const TextBlockData::Parenthesis* matchedPar = matchParenthesis(allParenthesis, thePar); + if (!matchedPar) + return; + + // Mark new match + markMatchedParenthesis(thePar->position, matchedPar->position, selections); + setExtraSelections(selections); +} + +void SqlEditor::markMatchedParenthesis(int pos1, int pos2, QList& selections) +{ + QTextEdit::ExtraSelection selection; + + selection.format.setBackground(CFG_UI.Colors.SqlEditorParenthesisBg.get()); + + QTextCursor cursor = textCursor(); + + cursor.setPosition(pos1); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + selection.cursor = cursor; + selections.append(selection); + + cursor.setPosition(pos2); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + selection.cursor = cursor; + selections.append(selection); +} + +void SqlEditor::doBackspace(int repeats) +{ + QTextCursor cursor = textCursor(); + for (int i = 0; i < repeats; i++) + cursor.deletePreviousChar(); +} + +void SqlEditor::indentSelected(bool shiftPressed) +{ + QTextCursor cursor = textCursor(); + QTextDocument* doc = document(); + QTextBlock startBlock = doc->findBlock(cursor.selectionStart()); + QTextBlock endBlock = doc->findBlock(cursor.selectionEnd()); + for (QTextBlock it = startBlock; it != endBlock; it = it.next()) + { + if (shiftPressed) + unindentBlock(it); + else + indentBlock(it); + } +} + +void SqlEditor::indentBlock(const QTextBlock& block) +{ + QTextCursor cursor = textCursor(); + cursor.setPosition(block.position()); + cursor.insertText(" "); +} + +void SqlEditor::unindentBlock(const QTextBlock& block) +{ + QString str = block.text(); + if (!str.startsWith(" ")) + return; + + int spaces = 0; + int firstPrintable = str.indexOf(QRegExp("\\S")); + if (firstPrintable == -1) + spaces = str.length(); + else + spaces = firstPrintable; + + QTextCursor cursor = textCursor(); + cursor.setPosition(block.position()); + for (int i = 0; i < 4 && i < spaces; i++) + cursor.deleteChar(); +} + +void SqlEditor::indentNewLine() +{ + QTextCursor cursor = textCursor(); + + // If there is no previous block to refer to, do nothing + QTextBlock previousBlock = document()->findBlockByNumber(cursor.blockNumber() - 1); + if (!previousBlock.isValid()) + return; + + // If previous block has first pritable character further than current cursor position, insert spaces to meet above position + int previousFirstPrintable = previousBlock.text().indexOf(QRegExp("\\S")); + if (previousFirstPrintable > 0) + { + insertPlainText(QString(" ").repeated(previousFirstPrintable)); + return; + } + +} + +void SqlEditor::showSearchDialog() +{ + if (!searchDialog) + { + searchDialog = new SearchTextDialog(textLocator, this); + searchDialog->setWindowTitle(tr("Find or replace", "sql editor find/replace dialog")); + } + + if (searchDialog->isVisible()) + searchDialog->hide(); + + searchDialog->show(); +} + +const TextBlockData::Parenthesis* SqlEditor::matchParenthesis(QList parList, + const TextBlockData::Parenthesis* thePar) +{ + bool matchLeftPar = (thePar->character == ')'); + char parToMatch = matchLeftPar ? '(' : ')'; + int parListSize = parList.size(); + int theParIdx = parList.indexOf(thePar); + int counter = 0; + for (int i = theParIdx; (matchLeftPar ? i >= 0 : i < parListSize); (matchLeftPar ? i-- : i++)) + { + if (parList[i]->character == parToMatch) + counter--; + else + counter++; + + if (counter == 0) + return parList[i]; + } + return nullptr; +} + +void SqlEditor::completerTypedText(const QString& text) +{ + insertPlainText(text); + completer->extendFilterBy(text); + updateCompleterPosition(); +} + +void SqlEditor::completerBackspacePressed() +{ + deletionKeyPressed = true; + textCursor().deletePreviousChar(); + completer->shringFilterBy(1); + updateCompleterPosition(); + deletionKeyPressed = false; +} + +void SqlEditor::completerLeftPressed() +{ + completer->shringFilterBy(1); + moveCursor(QTextCursor::Left); + updateCompleterPosition(); +} + +void SqlEditor::completerRightPressed() +{ + // Last character seems to be virtual in QPlainTextEdit, so that QTextCursor can be at its position + int charCnt = document()->characterCount() - 1; + int curPos = textCursor().position(); + + if (curPos >= charCnt) + { + completer->reject(); + return; + } + + QChar c = document()->characterAt(curPos); + if (!c.isNull()) + completer->extendFilterBy(QString(c)); + + moveCursor(QTextCursor::Right); + updateCompleterPosition(); +} + +void SqlEditor::parseContents() +{ + if (document()->characterCount() > 100000) + { + if (richFeaturesEnabled) + notifyWarn(tr("Contents of the SQL editor are huge, so errors detecting and existing objects highlighting are temporarily disabled.")); + + richFeaturesEnabled = false; + return; + } + else if (!richFeaturesEnabled) + { + richFeaturesEnabled = true; + } + + // Updating dialect according to current database (if any) + Dialect dialect = Dialect::Sqlite3; + if (db && db->isValid()) + dialect = db->getDialect(); + + QString sql = toPlainText(); + if (!virtualSqlExpression.isNull()) + { + if (virtualSqlCompleteSemicolon && !sql.trimmed().endsWith(";")) + sql += ";"; + + sql = virtualSqlExpression.arg(sql); + } + + queryParser->setDialect(dialect); + queryParser->parse(sql); + + checkForValidObjects(); + checkForSyntaxErrors(); + highlighter->rehighlight(); +} + +void SqlEditor::checkForSyntaxErrors() +{ + syntaxValidated = true; + + removeErrorMarkers(); + + // Marking invalid tokens, like in "SELECT * from test] t" - the "]" token is invalid. + // Such tokens don't cause parser to fail. + foreach (SqliteQueryPtr query, queryParser->getQueries()) + { + foreach (TokenPtr token, query->tokens) + { + if (token->type == Token::INVALID) + markErrorAt(token->start, token->end, true); + } + } + + if (queryParser->isSuccessful()) + { + emit errorsChecked(false); + return; + } + + // Setting new markers when errors were detected + foreach (ParserError* error, queryParser->getErrors()) + markErrorAt(sqlIndex(error->getFrom()), sqlIndex(error->getTo())); + + emit errorsChecked(true); +} + +void SqlEditor::checkForValidObjects() +{ + clearDbObjects(); + if (!db || !db->isValid()) + return; + + Dialect dialect = db->getDialect(); + QList fullObjects; + QString dbName; + foreach (SqliteQueryPtr query, queryParser->getQueries()) + { + fullObjects = query->getContextFullObjects(); + foreach (const SqliteStatement::FullObject& fullObj, fullObjects) + { + dbName = fullObj.database ? stripObjName(fullObj.database->value, dialect) : "main"; + if (!objectsInNamedDb.contains(dbName)) + continue; + + if (fullObj.type == SqliteStatement::FullObject::DATABASE) + { + // Valid db name + addDbObject(sqlIndex(fullObj.database->start), sqlIndex(fullObj.database->end), QString::null); + continue; + } + + if (!objectsInNamedDb[dbName].contains(stripObjName(fullObj.object->value, dialect))) + continue; + + // Valid object name + addDbObject(sqlIndex(fullObj.object->start), sqlIndex(fullObj.object->end), dbName); + } + } +} + +void SqlEditor::scheduleQueryParser(bool force) +{ + if (!document()->isModified() && !force) + return; + + syntaxValidated = false; + + document()->setModified(false); + queryParserTimer->stop(); + queryParserTimer->start(); + + scheduleAutoCompletion(); +} + +int SqlEditor::sqlIndex(int idx) +{ + if (virtualSqlExpression.isNull()) + return idx; + + if (idx < virtualSqlOffset) + return virtualSqlOffset; + + idx -= virtualSqlOffset; + + int lastIdx = toPlainText().length() - 1; + if (idx > lastIdx) + return lastIdx; + + return idx; +} + +void SqlEditor::updateLineNumberArea() +{ + updateLineNumberArea(viewport()->rect(), viewport()->y()); +} + +bool SqlEditor::hasSelection() const +{ + return textCursor().hasSelection(); +} + +void SqlEditor::replaceSelectedText(const QString &newText) +{ + textCursor().insertText(newText); +} + +QString SqlEditor::getSelectedText() const +{ + QString txt = textCursor().selectedText(); + fixTextCursorSelectedText(txt); + return txt; +} + +void SqlEditor::openObject(const QString& database, const QString& name) +{ + DbObjectDialogs dialogs(db); + dialogs.editObject(database, name); +} + +void SqlEditor::updateLineNumberAreaWidth() +{ + if (!showLineNumbers) + { + setViewportMargins(0, 0, 0, 0); + return; + } + + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); +} + +void SqlEditor::highlightCurrentLine() +{ + QList selections; + + if (!isReadOnly() && isEnabled()) + { + QTextEdit::ExtraSelection selection; + + selection.format.setBackground(CFG_UI.Colors.SqlEditorCurrentLineBg.get()); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + selections.append(selection); + } + + setExtraSelections(selections); +} + +void SqlEditor::updateLineNumberArea(const QRect& rect, int dy) +{ + if (!showLineNumbers) + { + updateLineNumberAreaWidth(); + return; + } + + if (dy) + lineNumberArea->scroll(0, dy); + else + lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); + + if (rect.contains(viewport()->rect())) + updateLineNumberAreaWidth(); +} + +void SqlEditor::cursorMoved() +{ + highlightParenthesis(); + + if (!cursorMovingByLocator) + { + textLocator->setStartPosition(textCursor().position()); + textLocator->cursorMoved(); + } +} + +void SqlEditor::formatSql() +{ + QString sql = hasSelection() ? getSelectedText() : toPlainText(); + sql = SQLITESTUDIO->getCodeFormatter()->format("sql", sql, db); + + if (!hasSelection()) + selectAll(); + + replaceSelectedText(sql); +} + +void SqlEditor::saveToFile() +{ + QString dir = getFileDialogInitPath(); + QString fName = QFileDialog::getSaveFileName(this, tr("Save to file"), dir); + if (fName.isNull()) + return; + + setFileDialogInitPathByFile(fName); + + QFile file(fName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + notifyError(tr("Could not open file '%1'' for writing: %2").arg(fName).arg(file.errorString())); + return; + } + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + stream << toPlainText(); + stream.flush(); + file.close(); +} + +void SqlEditor::loadFromFile() +{ + QString dir = getFileDialogInitPath(); + QString filters = tr("SQL scripts (*.sql);;All files (*)"); + QString fName = QFileDialog::getOpenFileName(this, tr("Open file"), dir, filters); + if (fName.isNull()) + return; + + setFileDialogInitPathByFile(fName); + + QFile file(fName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + notifyError(tr("Could not open file '%1'' for reading: %2").arg(fName).arg(file.errorString())); + return; + } + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + QString sql = stream.readAll(); + file.close(); + setPlainText(sql); +} + +void SqlEditor::deleteLine() +{ + QTextCursor cursor = textCursor(); + if (cursor.hasSelection()) + deleteSelectedLines(); + else + deleteCurrentLine(); +} + +void SqlEditor::deleteCurrentLine() +{ + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::StartOfLine); + cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + + QTextDocument* doc = document(); + QTextBlock block = doc->findBlock(cursor.position()); + if (block.next().isValid()) + cursor.deleteChar(); + else + { + cursor.deletePreviousChar(); + cursor.movePosition(QTextCursor::StartOfLine); + } + setTextCursor(cursor); +} + +void SqlEditor::deleteSelectedLines() +{ + QTextCursor cursor = textCursor(); + QTextDocument* doc = document(); + QTextBlock startBlock = doc->findBlock(cursor.selectionStart()); + QTextBlock endBlock = doc->findBlock(cursor.selectionEnd() - 1); + int idxMod = 0; + if (!endBlock.next().isValid()) // no newline at the end + idxMod = -1; + + cursor.setPosition(startBlock.position()); + cursor.setPosition(endBlock.position() + endBlock.length() + idxMod, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); +} +void SqlEditor::moveBlockDown(bool deleteOld) +{ + QTextCursor cursor = textCursor(); + if (!cursor.hasSelection()) + { + cursor.movePosition(QTextCursor::StartOfLine); + cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + } + + QTextDocument* doc = document(); + QTextBlock startBlock = doc->findBlock(cursor.selectionStart()); + QTextBlock endBlock = doc->findBlock(cursor.selectionEnd() - 1); + + QTextBlock nextBlock = endBlock.next(); + QTextBlock blockBeforeNewText = endBlock; + + // When moving text, we next block to be valid and operate on one after that + if (deleteOld) + { + if (!nextBlock.isValid()) + return; + + blockBeforeNewText = nextBlock; + nextBlock = nextBlock.next(); + } + + // If next block is invalid, we need to create it + bool removeLastNewLine = false; + if (!nextBlock.isValid()) + { + cursor.setPosition(blockBeforeNewText.position()); + cursor.movePosition(QTextCursor::EndOfLine); + cursor.insertBlock(); + nextBlock = blockBeforeNewText.next(); + removeLastNewLine = true; + } + + int textLength = endBlock.position() + endBlock.length() - startBlock.position(); + + // Collecting text and removing text from old position (if not copying) + cursor.setPosition(startBlock.position()); + cursor.setPosition(startBlock.position() + textLength, QTextCursor::KeepAnchor); + QString text = cursor.selectedText(); + fixTextCursorSelectedText(text); + if (deleteOld) // this is false when just copying + cursor.removeSelectedText(); + + // Pasting text at new position and reselecting it + cursor.setPosition(nextBlock.position()); + cursor.insertText(text); + cursor.setPosition(nextBlock.position() + textLength); + if (removeLastNewLine) // this is done when we moved to the last line, created block and copied another \n to it + cursor.deletePreviousChar(); + + cursor.setPosition(nextBlock.position(), QTextCursor::KeepAnchor); + setTextCursor(cursor); +} + +void SqlEditor::moveBlockUp(bool deleteOld) +{ + QTextCursor cursor = textCursor(); + if (!cursor.hasSelection()) + { + cursor.movePosition(QTextCursor::StartOfLine); + cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + } + + QTextDocument* doc = document(); + QTextBlock startBlock = doc->findBlock(cursor.selectionStart()); + QTextBlock endBlock = doc->findBlock(cursor.selectionEnd() - 1); + bool hasNewLineChar = endBlock.next().isValid(); + + QTextBlock insertingBlock = startBlock; + if (deleteOld) + { + insertingBlock = startBlock.previous(); + if (!insertingBlock.isValid()) + return; + } + + // We will operate on full line length, unless next block was invalid, thus at the end there's no new line. + int textLength = endBlock.position() + endBlock.length() - startBlock.position(); + if (!hasNewLineChar) + textLength--; + + // Collecting text and removing text from old position (if not copying) + cursor.setPosition(startBlock.position()); + cursor.setPosition(startBlock.position() + textLength, QTextCursor::KeepAnchor); + QString text = cursor.selectedText(); + fixTextCursorSelectedText(text); + if (deleteOld) // this is false when just copying + cursor.removeSelectedText(); + + // Pasting text at new position + cursor.setPosition(insertingBlock.position()); + cursor.insertText(text); + if (!hasNewLineChar) + { + cursor.insertBlock(); + cursor.setPosition(insertingBlock.next().next().position()); + cursor.deletePreviousChar(); + textLength++; // we will need to include "new line" when reselecting text + } + + // Reselecting new text + cursor.setPosition(insertingBlock.position() + textLength); + cursor.setPosition(insertingBlock.position(), QTextCursor::KeepAnchor); + setTextCursor(cursor); +} + +void SqlEditor::copyBlockDown() +{ + moveBlockDown(false); +} + +void SqlEditor::copyBlockUp() +{ + moveBlockUp(false); +} + +void SqlEditor::find() +{ + textLocator->setStartPosition(textCursor().position()); + showSearchDialog(); +} + +void SqlEditor::findNext() +{ + textLocator->findNext(); +} + +void SqlEditor::findPrevious() +{ + textLocator->findPrev(); +} + +void SqlEditor::replace() +{ + showSearchDialog(); +} + +void SqlEditor::found(int start, int end) +{ + QTextCursor cursor = textCursor(); + cursor.setPosition(end); + cursor.setPosition(start, QTextCursor::KeepAnchor); + cursorMovingByLocator = true; + setTextCursor(cursor); + cursorMovingByLocator = false; + ensureCursorVisible(); +} + +void SqlEditor::reachedEnd() +{ + notifyInfo(tr("Reached the end of document. Hit the find again to restart the search.")); +} + +void SqlEditor::changeFont(const QVariant& font) +{ + setFont(font.value()); +} + +void SqlEditor::configModified() +{ + highlighter->rehighlight(); +} + +void SqlEditor::keyPressEvent(QKeyEvent* e) +{ + switch (e->key()) + { + case Qt::Key_Backspace: + { + deletionKeyPressed = true; + if (e->modifiers().testFlag(Qt::NoModifier)) + backspacePressed(); + else + QPlainTextEdit::keyPressEvent(e); + deletionKeyPressed = false; + return; + } + case Qt::Key_Delete: + { + deletionKeyPressed = true; + QPlainTextEdit::keyPressEvent(e); + deletionKeyPressed = false; + return; + } + case Qt::Key_Home: + { + homePressed(e->modifiers()); + return; + } + case Qt::Key_Tab: + { + tabPressed(e->modifiers().testFlag(Qt::ShiftModifier)); + return; + } + case Qt::Key_Backtab: + { + tabPressed(true); + return; + } + case Qt::Key_Return: + case Qt::Key_Enter: + { + QPlainTextEdit::keyPressEvent(e); + indentNewLine(); + return; + } + case Qt::Key_Control: + setObjectLinks(true); + break; + default: + break; + } + QPlainTextEdit::keyPressEvent(e); +} + +void SqlEditor::keyReleaseEvent(QKeyEvent* e) +{ + if (e->key() == Qt::Key_Control) + setObjectLinks(false); + + QPlainTextEdit::keyReleaseEvent(e); +} + +void SqlEditor::focusOutEvent(QFocusEvent* e) +{ + UNUSED(e); + setObjectLinks(false); + QPlainTextEdit::focusOutEvent(e); +} + +void SqlEditor::focusInEvent(QFocusEvent* e) +{ + if (completer->isVisible()) + { + // Sometimes, when switching to other application window and then getting back to SQLiteStudio, + // the completer loses focus, but doesn't close. In that case, the SqlEditor gets focused, + // while completer still exists. Here we fix this case. + completer->reject(); + return; + } + + QPlainTextEdit::focusInEvent(e); +} + +void SqlEditor::mouseMoveEvent(QMouseEvent* e) +{ + handleValidObjectCursor(e->pos()); + QPlainTextEdit::mouseMoveEvent(e); +} + +void SqlEditor::mousePressEvent(QMouseEvent* e) +{ + if (objectLinksEnabled) + { + const DbObject* obj = getValidObjectForPosition(e->pos()); + if (obj && e->button() == Qt::LeftButton) + { + QString objName = toPlainText().mid(obj->from, (obj->to - obj->from + 1)); + openObject(obj->dbName, objName); + } + } + + QPlainTextEdit::mousePressEvent(e); +} + +void SqlEditor::resizeEvent(QResizeEvent* e) +{ + QPlainTextEdit::resizeEvent(e); + QRect cr = contentsRect(); + lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); +} + +void SqlEditor::handleValidObjectCursor(const QPoint& point) +{ + if (!objectLinksEnabled) + return; + + QTextCursor cursor = cursorForPosition(point); + int position = cursor.position(); + QRect curRect = cursorRect(cursor); + bool isValid = false; + if (point.y() >= curRect.top() && point.y() <= curRect.bottom()) + { + // Mouse pointer is at the same line as cursor, so cursor was returned for actual character under mouse + // and not just first/last character of the line, because mouse was out of text. + bool movedLeft = (curRect.x() - point.x()) < 0; + isValid = (getValidObjectForPosition(position, movedLeft) != nullptr); + } + viewport()->setCursor(isValid ? Qt::PointingHandCursor : Qt::IBeamCursor); +} +bool SqlEditor::getVirtualSqlCompleteSemicolon() const +{ + return virtualSqlCompleteSemicolon; +} + +void SqlEditor::setVirtualSqlCompleteSemicolon(bool value) +{ + virtualSqlCompleteSemicolon = value; +} + +bool SqlEditor::getShowLineNumbers() const +{ + return showLineNumbers; +} + +void SqlEditor::setShowLineNumbers(bool value) +{ + showLineNumbers = value; + lineNumberArea->setVisible(value); + updateLineNumberArea(); +} + +void SqlEditor::checkSyntaxNow() +{ + queryParserTimer->stop(); + parseContents(); +} + +void SqlEditor::saveSelection() +{ + QTextCursor cur = textCursor(); + storedSelectionStart = cur.selectionStart(); + storedSelectionEnd = cur.selectionEnd(); +} + +void SqlEditor::restoreSelection() +{ + QTextCursor cur = textCursor(); + cur.setPosition(storedSelectionStart); + cur.setPosition(storedSelectionEnd, QTextCursor::KeepAnchor); +} + +QToolBar* SqlEditor::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return nullptr; +} + +QString SqlEditor::getVirtualSqlExpression() const +{ + return virtualSqlExpression; +} + +void SqlEditor::setVirtualSqlExpression(const QString& value) +{ + virtualSqlExpression = value; + + virtualSqlOffset = virtualSqlExpression.indexOf("%1"); + if (virtualSqlOffset == -1) + { + virtualSqlOffset = 0; + virtualSqlExpression = QString::null; + qWarning() << "Tried to set invalid virtualSqlExpression for SqlEditor. Ignored."; + return; + } + + virtualSqlRightOffset = virtualSqlExpression.length() - virtualSqlOffset - 2; +} + +void SqlEditor::setTriggerContext(const QString& table) +{ + createTriggerTable = table; + highlighter->setCreateTriggerContext(!table.isEmpty()); +} + +const SqlEditor::DbObject* SqlEditor::getValidObjectForPosition(const QPoint& point) +{ + QTextCursor cursor = cursorForPosition(point); + int position = cursor.position(); + bool movedLeft = (cursorRect(cursor).x() - point.x()) < 0; + return getValidObjectForPosition(position, movedLeft); +} + +const SqlEditor::DbObject* SqlEditor::getValidObjectForPosition(int position, bool movedLeft) +{ + foreach (const DbObject& obj, validDbObjects) + { + if ((!movedLeft && position > obj.from && position-1 <= obj.to) || + (movedLeft && position >= obj.from && position <= obj.to)) + { + return &obj; + } + } + return nullptr; +} + +SqlEditor::DbObject::DbObject(int from, int to, const QString& dbName) : + from(from), to(to), dbName(dbName) +{ + +} + +SqlEditor::LineNumberArea::LineNumberArea(SqlEditor* editor) : + QWidget(editor), codeEditor(editor) +{ +} + +QSize SqlEditor::LineNumberArea::sizeHint() const +{ + return QSize(codeEditor->lineNumberAreaWidth(), 0); +} + +void SqlEditor::LineNumberArea::paintEvent(QPaintEvent* event) +{ + if (codeEditor->getShowLineNumbers()) + codeEditor->lineNumberAreaPaintEvent(event); +} + + +void SqlEditor::changeEvent(QEvent* e) +{ + if (e->type() == QEvent::EnabledChange) + highlightCurrentLine(); + + QPlainTextEdit::changeEvent(e); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/sqleditor.h b/SQLiteStudio3/guiSQLiteStudio/sqleditor.h new file mode 100644 index 0000000..d525e20 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/sqleditor.h @@ -0,0 +1,280 @@ +#ifndef SQLEDITOR_H +#define SQLEDITOR_H + +#include "guiSQLiteStudio_global.h" +#include "common/extactioncontainer.h" +#include "db/db.h" +#include "sqlitesyntaxhighlighter.h" +#include +#include +#include +#include + +class CompleterWindow; +class QTimer; +class Parser; +class SqlEditor; +class SearchTextDialog; +class SearchTextLocator; + +CFG_KEY_LIST(SqlEditor, QObject::tr("SQL editor input field"), + CFG_KEY_ENTRY(CUT, QKeySequence::Cut, QObject::tr("Cut selected text")) + CFG_KEY_ENTRY(COPY, QKeySequence::Copy, QObject::tr("Copy selected text")) + CFG_KEY_ENTRY(PASTE, QKeySequence::Paste, QObject::tr("Paste from clipboard")) + CFG_KEY_ENTRY(DELETE, QKeySequence::Delete, QObject::tr("Delete selected text")) + CFG_KEY_ENTRY(SELECT_ALL, QKeySequence::SelectAll, QObject::tr("Select whole editor contents")) + CFG_KEY_ENTRY(UNDO, QKeySequence::Undo, QObject::tr("Undo")) + CFG_KEY_ENTRY(REDO, QKeySequence::Redo, QObject::tr("Redo")) + CFG_KEY_ENTRY(SAVE_SQL_FILE, QKeySequence::Save, QObject::tr("Save contents into a file")) + CFG_KEY_ENTRY(OPEN_SQL_FILE, QKeySequence::Open, QObject::tr("Load contents from a file")) + CFG_KEY_ENTRY(FIND, QKeySequence::Find, QObject::tr("Find in text")) + CFG_KEY_ENTRY(FIND_NEXT, QKeySequence::FindNext, QObject::tr("Find next")) + CFG_KEY_ENTRY(FIND_PREV, QKeySequence::FindPrevious, QObject::tr("Find previous")) + CFG_KEY_ENTRY(REPLACE, QKeySequence::Replace, QObject::tr("Replace in text")) + CFG_KEY_ENTRY(DELETE_LINE, Qt::CTRL + Qt::Key_D, QObject::tr("Delete current line")) + CFG_KEY_ENTRY(COMPLETE, Qt::CTRL + Qt::Key_Space, QObject::tr("Request code assistant")) + CFG_KEY_ENTRY(FORMAT_SQL, Qt::CTRL + Qt::Key_T, QObject::tr("Format contents")) + CFG_KEY_ENTRY(MOVE_BLOCK_DOWN, Qt::ALT + Qt::Key_Down, QObject::tr("Move selected block of text one line down")) + CFG_KEY_ENTRY(MOVE_BLOCK_UP, Qt::ALT + Qt::Key_Up, QObject::tr("Move selected block of text one line up")) + CFG_KEY_ENTRY(COPY_BLOCK_DOWN, Qt::ALT + Qt::CTRL + Qt::Key_Down, QObject::tr("Copy selected block of text and paste it a line below")) + CFG_KEY_ENTRY(COPY_BLOCK_UP, Qt::ALT + Qt::CTRL + Qt::Key_Up, QObject::tr("Copy selected block of text and paste it a line above")) +) + +class GUI_API_EXPORT SqlEditor : public QPlainTextEdit, public ExtActionContainer +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + COPY, + PASTE, + CUT, + UNDO, + REDO, + DELETE, + SELECT_ALL, + FORMAT_SQL, + OPEN_SQL_FILE, + SAVE_SQL_FILE, + DELETE_LINE, + COMPLETE, + MOVE_BLOCK_DOWN, + MOVE_BLOCK_UP, + COPY_BLOCK_DOWN, + COPY_BLOCK_UP, + FIND, + FIND_NEXT, + FIND_PREV, + REPLACE + }; + + enum ToolBar + { + }; + + explicit SqlEditor(QWidget *parent = 0); + ~SqlEditor(); + + Db* getDb() const; + void setDb(Db* value); + void setAutoCompletion(bool enabled); + QString getVirtualSqlExpression() const; + void setVirtualSqlExpression(const QString& value); + void setTriggerContext(const QString& table); + bool haveErrors(); + bool isSyntaxChecked(); + bool getShowLineNumbers() const; + void setShowLineNumbers(bool value); + void checkSyntaxNow(); + void saveSelection(); + void restoreSelection(); + QToolBar* getToolBar(int toolbar) const; + + bool getVirtualSqlCompleteSemicolon() const; + void setVirtualSqlCompleteSemicolon(bool value); + + protected: + void setupDefShortcuts(); + void createActions(); + void keyPressEvent(QKeyEvent* e); + void keyReleaseEvent(QKeyEvent* e); + void focusOutEvent(QFocusEvent* e); + void focusInEvent(QFocusEvent* e); + void mouseMoveEvent(QMouseEvent* e); + void mousePressEvent(QMouseEvent* e); + void resizeEvent(QResizeEvent *e); + void changeEvent(QEvent*e); + + private: + class LineNumberArea : public QWidget + { + public: + explicit LineNumberArea(SqlEditor *editor); + QSize sizeHint() const; + + protected: + void paintEvent(QPaintEvent *event); + + private: + SqlEditor *codeEditor = nullptr; + }; + + struct DbObject + { + DbObject(int from, int to, const QString& dbName); + + int from; + int to; + + /** + * @brief dbName + * Attach name for the db that object belongs to. + * If the object is database itself, then this variable is null. + */ + QString dbName; + }; + + void setupMenu(); + void updateCompleterPosition(); + void init(); + void removeErrorMarkers(); + void deleteCurrentLine(); + void deleteSelectedLines(); + + /** + * @brief markErrorAt Mark error range. + * @param start Start index of error. + * @param end End index of error. + * @param limitedDamage true if error is just invalid token, that didn't cause parser to fail. + */ + void markErrorAt(int start, int end, bool limitedDamage = false); + void deletePreviousChars(int length = 1); + void refreshValidObjects(); + void checkForSyntaxErrors(); + void checkForValidObjects(); + Dialect getDialect(); + void setObjectLinks(bool enabled); + void addDbObject(int from, int to, const QString& dbName); + void clearDbObjects(); + void lineNumberAreaPaintEvent(QPaintEvent* event); + int lineNumberAreaWidth(); + void highlightParenthesis(); + const TextBlockData::Parenthesis* matchParenthesis(QList parList, const TextBlockData::Parenthesis* thePar); + void markMatchedParenthesis(int pos1, int pos2, QList& selections); + void doBackspace(int repeats = 1); + void indentSelected(bool shiftPressed); + void indentBlock(const QTextBlock& block); + void unindentBlock(const QTextBlock& block); + void indentNewLine(); + void showSearchDialog(); + int sqlIndex(int idx); + void updateLineNumberArea(); + bool hasSelection() const; + void replaceSelectedText(const QString& newText); + QString getSelectedText() const; + void openObject(const QString& database, const QString& name); + + /** + * @brief getValidObjectForPosition + * @param position Cursor text position determinated by Qt mouse event. + * @param movedLeft true if Qt moved cursor left from click point, which means that user clicked closer to left border of character. Otherwise cursor was moved towards right. + * @return Object identified under given text position, or null if there was no valid object under that position. + */ + const DbObject* getValidObjectForPosition(int position, bool movedLeft); + const DbObject* getValidObjectForPosition(const QPoint& point); + void handleValidObjectCursor(const QPoint& point); + + SqliteSyntaxHighlighter* highlighter = nullptr; + QMenu* contextMenu = nullptr; + QMenu* validObjContextMenu = nullptr; + Db* db = nullptr; + CompleterWindow* completer = nullptr; + QTimer* autoCompleteTimer = nullptr; + bool autoCompletion = true; + bool deletionKeyPressed = false; + QTimer* queryParserTimer = nullptr; + Parser* queryParser = nullptr; + QHash objectsInNamedDb; + bool objectLinksEnabled = false; + QList validDbObjects; + QWidget* lineNumberArea = nullptr; + SearchTextDialog* searchDialog = nullptr; + SearchTextLocator* textLocator = nullptr; + bool cursorMovingByLocator = false; + bool syntaxValidated = false; + bool showLineNumbers = true; + int storedSelectionStart = 0; + int storedSelectionEnd = 0; + bool richFeaturesEnabled = true; + + /** + * @brief virtualSqlExpression + * It has to be an SQL string containing exactly one argument %1 (as Qt string arguments). + * It will be used in every syntax completion request as a template, as if user + * wrote this entire SQL statement, plus his own code in place of %1 and then the completer is invoked. + * User never sees this SQL expression, it's hidden from him. + * The expression will also be used for syntax error checking the same was as for completer. + * + * This is useful for example when we want to have a context for completion in CHECK() constraint, + * but user has only input edit for the CHECK expression itself, so for completer to work correctly + * it needs to be lied that there is entire "CREATE TABLE...CHECK(" before the users code. In that + * case you would set this variable to something like this: "CREATE TABLE x (c CHECK(%1))". + * The SqlEditor is smart enough to do all the magic given the above expression. + */ + QString virtualSqlExpression; + int virtualSqlOffset = 0; + int virtualSqlRightOffset = 0; + bool virtualSqlCompleteSemicolon = false; + QString createTriggerTable; + + static const int autoCompleterDelay = 300; + static const int queryParserDelay = 500; + + private slots: + void customContextMenuRequested(const QPoint& pos); + void updateUndoAction(bool enabled); + void updateRedoAction(bool enabled); + void updateCopyAction(bool enabled); + void deleteSelected(); + void homePressed(Qt::KeyboardModifiers modifiers); + void tabPressed(bool shiftPressed); + void backspacePressed(); + void complete(); + void completeSelected(); + void scheduleAutoCompletion(); + void checkForAutoCompletion(); + void completerTypedText(const QString& text); + void completerBackspacePressed(); + void completerLeftPressed(); + void completerRightPressed(); + void parseContents(); + void scheduleQueryParser(bool force = false); + void updateLineNumberAreaWidth(); + void highlightCurrentLine(); + void updateLineNumberArea(const QRect&rect, int dy); + void cursorMoved(); + void formatSql(); + void saveToFile(); + void loadFromFile(); + void deleteLine(); + void moveBlockDown(bool deleteOld = true); + void moveBlockUp(bool deleteOld = true); + void copyBlockDown(); + void copyBlockUp(); + void find(); + void findNext(); + void findPrevious(); + void replace(); + void found(int start, int end); + void reachedEnd(); + void changeFont(const QVariant& font); + void configModified(); + + signals: + void errorsChecked(bool haveErrors); +}; + + +#endif // SQLEDITOR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp b/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp new file mode 100644 index 0000000..e3e1950 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.cpp @@ -0,0 +1,447 @@ +#include "sqlitesyntaxhighlighter.h" +#include "parser/lexer.h" +#include "uiconfig.h" +#include "services/config.h" +#include +#include +#include + +SqliteSyntaxHighlighter::SqliteSyntaxHighlighter(QTextDocument *parent) : + QSyntaxHighlighter(parent) +{ + setupFormats(); + setupMapping(); + setCurrentBlockState(regulartTextBlockState); + connect(CFG, SIGNAL(massSaveCommited()), this, SLOT(setupFormats())); +} + +void SqliteSyntaxHighlighter::setSqliteVersion(int version) +{ + this->sqliteVersion = version; + rehighlight(); +} + +void SqliteSyntaxHighlighter::setFormat(SqliteSyntaxHighlighter::State state, QTextCharFormat format) +{ + formats[state] = format; +} + +QTextCharFormat SqliteSyntaxHighlighter::getFormat(SqliteSyntaxHighlighter::State state) const +{ + return formats[state]; +} + +void SqliteSyntaxHighlighter::setupFormats() +{ + QTextCharFormat format; + + // Standard + format.setForeground(CFG_UI.Colors.SqlEditorForeground.get()); + format.setFontWeight(QFont::Normal); + format.setFontItalic(false); + formats[State::STANDARD] = format; + + // Parenthesis + formats[State::PARENTHESIS] = format; + + // String + format.setForeground(CFG_UI.Colors.SqlEditorStringFg.get()); + format.setFontWeight(QFont::Normal); + format.setFontItalic(false); + formats[State::STRING] = format; + + // Keyword + format.setForeground(CFG_UI.Colors.SqlEditorKeywordFg.get()); + format.setFontWeight(QFont::Bold); + format.setFontItalic(false); + formats[State::KEYWORD] = format; + + // BindParam + format.setForeground(CFG_UI.Colors.SqlEditorBindParamFg.get()); + format.setFontWeight(QFont::Normal); + format.setFontItalic(false); + formats[State::BIND_PARAM] = format; + + // Blob + format.setForeground(CFG_UI.Colors.SqlEditorBlobFg.get()); + format.setFontWeight(QFont::Normal); + format.setFontItalic(false); + formats[State::BLOB] = format; + + // Comment + format.setForeground(CFG_UI.Colors.SqlEditorCommentFg.get()); + format.setFontWeight(QFont::Normal); + format.setFontItalic(true); + formats[State::COMMENT] = format; + + // Number + format.setForeground(CFG_UI.Colors.SqlEditorNumberFg.get()); + format.setFontWeight(QFont::Normal); + format.setFontItalic(false); + formats[State::NUMBER] = format; +} + +void SqliteSyntaxHighlighter::setupMapping() +{ + tokenTypeMapping[Token::STRING] = State::STRING; + tokenTypeMapping[Token::COMMENT] = State::COMMENT; + tokenTypeMapping[Token::FLOAT] = State::NUMBER; + tokenTypeMapping[Token::INTEGER] = State::NUMBER; + tokenTypeMapping[Token::BIND_PARAM] = State::BIND_PARAM; + tokenTypeMapping[Token::PAR_LEFT] = State::PARENTHESIS; + tokenTypeMapping[Token::PAR_RIGHT] = State::PARENTHESIS; + tokenTypeMapping[Token::BLOB] = State::BLOB; + tokenTypeMapping[Token::KEYWORD] = State::KEYWORD; +} + +QString SqliteSyntaxHighlighter::getPreviousStatePrefix(TextBlockState textBlockState) +{ + QString prefix = ""; + switch (textBlockState) + { + case SqliteSyntaxHighlighter::TextBlockState::REGULAR: + break; + case SqliteSyntaxHighlighter::TextBlockState::BLOB: + prefix = "x'"; + break; + case SqliteSyntaxHighlighter::TextBlockState::STRING: + prefix = "'"; + break; + case SqliteSyntaxHighlighter::TextBlockState::COMMENT: + prefix = "/*"; + break; + case SqliteSyntaxHighlighter::TextBlockState::ID_1: + prefix = "["; + break; + case SqliteSyntaxHighlighter::TextBlockState::ID_2: + prefix = "\""; + break; + case SqliteSyntaxHighlighter::TextBlockState::ID_3: + prefix = "`"; + break; + } + return prefix; +} + +void SqliteSyntaxHighlighter::highlightBlock(const QString &text) +{ + if (text.length() <= 0) + return; + + // Reset to default + QSyntaxHighlighter::setFormat(0, text.length(), formats[State::STANDARD]); + + qint32 idxModifier = 0; + QString statePrefix = ""; + if (previousBlockState() != regulartTextBlockState) + { + statePrefix = getPreviousStatePrefix(static_cast(previousBlockState())); + idxModifier += statePrefix.size(); + } + + Lexer lexer(sqliteVersion == 2 ? Dialect::Sqlite2 : Dialect::Sqlite3); + lexer.setTolerantMode(true); + lexer.prepare(statePrefix+text); + + // Previous error state. + // Empty lines have no userData, so we will look for any previous paragraph that is + // valid and has a data, so it has any logical meaning to highlighter. + QTextBlock prevBlock = currentBlock().previous(); + while ((!prevBlock.isValid() || !prevBlock.userData() || prevBlock.text().isEmpty()) && prevBlock.position() > 0) + prevBlock = prevBlock.previous(); + + TextBlockData* prevData = nullptr; + if (prevBlock.isValid()) + prevData = dynamic_cast(prevBlock.userData()); + + TextBlockData* data = new TextBlockData(); + int errorStart = -1; + TokenPtr token = lexer.getToken(); + while (token) + { + if (handleToken(token, idxModifier, errorStart, data, prevData)) + errorStart = token->start + currentBlock().position(); + + if (data->getEndsWithQuerySeparator()) + errorStart = -1; + + handleParenthesis(token, data); + token = lexer.getToken(); + } + + setCurrentBlockUserData(data); +} + +bool SqliteSyntaxHighlighter::handleToken(TokenPtr token, qint32 idxModifier, int errorStart, TextBlockData* currBlockData, + TextBlockData* previousBlockData) +{ + qint64 start = token->start - idxModifier; + qint64 lgt = token->end - token->start + 1; + if (start < 0) + { + lgt += start; // cut length by num of chars before 0 (after idxModifier applied) + start = 0; + } + + if (createTriggerContext && token->type == Token::OTHER && (token->value.toLower() == "old" || token->value.toLower() == "new")) + token->type = Token::KEYWORD; + + bool limitedDamage = false; + bool querySeparator = (token->type == Token::Type::OPERATOR && token->value == ";"); + bool error = isError(start, lgt, &limitedDamage); + bool valid = isValid(start, lgt); + bool wasError = ( + (errorStart > -1) && + (start + currentBlock().position() + lgt >= errorStart) && + !currBlockData->getEndsWithQuerySeparator() // if it was set for previous token in the same block + ) || + ( + token->start == 0 && + previousBlockData && + previousBlockData->getEndsWithError() && + !previousBlockData->getEndsWithQuerySeparator() + ); + bool fatalError = (error && !limitedDamage) || wasError; + + QTextCharFormat format; + + // Applying valid object format. + applyValidObjectFormat(format, valid, error, wasError); + + // Get format for token type (if any) + if (tokenTypeMapping.contains(token->type)) + format = formats[tokenTypeMapping[token->type]]; + + // Merge with error format (if this is an error). + applyErrorFormat(format, error, wasError, token->type); + + // Apply format + QSyntaxHighlighter::setFormat(start, lgt, format); + + // Save block state + TolerantTokenPtr tolerantToken = token.dynamicCast(); + if (tolerantToken->invalid) + setStateForUnfinishedToken(tolerantToken); + else + setCurrentBlockState(regulartTextBlockState); + + currBlockData->setEndsWithError(fatalError); + currBlockData->setEndsWithQuerySeparator(querySeparator); + + return fatalError; +} + +void SqliteSyntaxHighlighter::applyErrorFormat(QTextCharFormat& format, bool isError, bool wasError, Token::Type tokenType) +{ + if ((!isError && !wasError) || tokenType == Token::Type::COMMENT) + return; + + format.setUnderlineStyle(QTextCharFormat::WaveUnderline); + format.setUnderlineColor(QColor(Qt::red)); +} + +void SqliteSyntaxHighlighter::applyValidObjectFormat(QTextCharFormat& format, bool isValid, bool isError, bool wasError) +{ + if (isError || wasError || !isValid) + return; + + format.setForeground(CFG_UI.Colors.SqlEditorValidObject.get()); + if (objectLinksEnabled) + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); +} + +void SqliteSyntaxHighlighter::handleParenthesis(TokenPtr token, TextBlockData* data) +{ + if (token->type == Token::PAR_LEFT || token->type == Token::PAR_RIGHT) + data->insertParenthesis(currentBlock().position() + token->start, token->value[0].toLatin1()); +} +bool SqliteSyntaxHighlighter::getCreateTriggerContext() const +{ + return createTriggerContext; +} + +void SqliteSyntaxHighlighter::setCreateTriggerContext(bool value) +{ + createTriggerContext = value; +} + + +bool SqliteSyntaxHighlighter::getObjectLinksEnabled() const +{ + return objectLinksEnabled; +} + +void SqliteSyntaxHighlighter::setObjectLinksEnabled(bool value) +{ + objectLinksEnabled = value; +} + +bool SqliteSyntaxHighlighter::isError(int start, int lgt, bool* limitedDamage) +{ + start += currentBlock().position(); + int end = start + lgt - 1; + foreach (const Error& error, errors) + { + if (error.from <= start && error.to >= end) + { + *limitedDamage = error.limitedDamage; + return true; + } + } + return false; +} + +bool SqliteSyntaxHighlighter::isValid(int start, int lgt) +{ + start += currentBlock().position(); + int end = start + lgt - 1; + foreach (const DbObject& obj, dbObjects) + { + if (obj.from <= start && obj.to >= end) + return true; + } + return false; +} + +void SqliteSyntaxHighlighter::setStateForUnfinishedToken(TolerantTokenPtr tolerantToken) +{ + switch (tolerantToken->type) + { + case Token::OTHER: + { + switch (tolerantToken->value.at(0).toLatin1()) + { + case '[': + setCurrentBlockState(static_cast(TextBlockState::ID_1)); + break; + case '"': + setCurrentBlockState(static_cast(TextBlockState::ID_2)); + break; + case '`': + setCurrentBlockState(static_cast(TextBlockState::ID_3)); + break; + } + break; + } + case Token::STRING: + setCurrentBlockState(static_cast(TextBlockState::STRING)); + break; + case Token::COMMENT: + setCurrentBlockState(static_cast(TextBlockState::COMMENT)); + break; + case Token::BLOB: + setCurrentBlockState(static_cast(TextBlockState::BLOB)); + break; + default: + break; + } +} +void SqliteSyntaxHighlighter::clearErrors() +{ + errors.clear(); +} + +bool SqliteSyntaxHighlighter::haveErrors() +{ + return errors.count() > 0; +} + +void SqliteSyntaxHighlighter::addDbObject(int from, int to) +{ + dbObjects << DbObject(from, to); +} + +void SqliteSyntaxHighlighter::clearDbObjects() +{ + dbObjects.clear(); +} + +void SqliteSyntaxHighlighter::addError(int from, int to, bool limitedDamage) +{ + errors << Error(from, to, limitedDamage); +} + +SqliteSyntaxHighlighter::Error::Error(int from, int to, bool limitedDamage) : + from(from), to(to), limitedDamage(limitedDamage) +{ +} + +int qHash(SqliteSyntaxHighlighter::State state) +{ + return static_cast(state); +} + + +SqliteSyntaxHighlighter::DbObject::DbObject(int from, int to) : + from(from), to(to) +{ +} + +QList TextBlockData::parentheses() +{ + QList list; + foreach (const TextBlockData::Parenthesis& par, parData) + list << ∥ + + return list; +} + +void TextBlockData::insertParenthesis(int pos, char c) +{ + Parenthesis par; + par.character = c; + par.position = pos; + parData << par; +} + +const TextBlockData::Parenthesis* TextBlockData::parenthesisForPosision(int pos) +{ + foreach (const Parenthesis& par, parData) + { + if (par.position == pos) + return ∥ + } + return nullptr; +} +bool TextBlockData::getEndsWithError() const +{ + return endsWithError; +} + +void TextBlockData::setEndsWithError(bool value) +{ + endsWithError = value; +} +bool TextBlockData::getEndsWithQuerySeparator() const +{ + return endsWithQuerySeparator; +} + +void TextBlockData::setEndsWithQuerySeparator(bool value) +{ + endsWithQuerySeparator = value; +} + + +int TextBlockData::Parenthesis::operator==(const TextBlockData::Parenthesis& other) +{ + return other.position == position && other.character == character; +} + +QString SqliteHighlighterPlugin::getLanguageName() const +{ + return "SQL"; +} + +QSyntaxHighlighter* SqliteHighlighterPlugin::createSyntaxHighlighter(QWidget* textEdit) const +{ + QPlainTextEdit* plainEdit = dynamic_cast(textEdit); + if (plainEdit) + return new SqliteSyntaxHighlighter(plainEdit->document()); + + QTextEdit* edit = dynamic_cast(textEdit); + if (edit) + return new SqliteSyntaxHighlighter(edit->document()); + + return nullptr; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h b/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h new file mode 100644 index 0000000..0696d02 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h @@ -0,0 +1,185 @@ +#ifndef SQLITESYNTAXHIGHLIGHTER_H +#define SQLITESYNTAXHIGHLIGHTER_H + +#include "parser/token.h" +#include "syntaxhighlighterplugin.h" +#include "plugins/builtinplugin.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class QWidget; + +class GUI_API_EXPORT TextBlockData : public QTextBlockUserData +{ + public: + struct GUI_API_EXPORT Parenthesis + { + char character; + int position; + int operator==(const Parenthesis& other); + }; + + QList parentheses(); + void insertParenthesis(int pos, char c); + const Parenthesis* parenthesisForPosision(int pos); + + bool getEndsWithError() const; + void setEndsWithError(bool value); + + bool getEndsWithQuerySeparator() const; + void setEndsWithQuerySeparator(bool value); + + private: + QList parData; + bool endsWithError = false; + bool endsWithQuerySeparator = false; +}; + +class GUI_API_EXPORT SqliteSyntaxHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + public: + enum class State + { + STANDARD, + PARENTHESIS, + STRING, + KEYWORD, + BIND_PARAM, + BLOB, + COMMENT, + NUMBER + }; + + explicit SqliteSyntaxHighlighter(QTextDocument *parent); + + void setSqliteVersion(int version); + void setFormat(State state, QTextCharFormat format); + QTextCharFormat getFormat(State state) const; + + void addError(int from, int to, bool limitedDamage = false); + void clearErrors(); + bool haveErrors(); + + void addDbObject(int from, int to); + void clearDbObjects(); + + bool getObjectLinksEnabled() const; + void setObjectLinksEnabled(bool value); + + void addCustomBgColor(int from, int to, const QColor& color); + void clearCustomBgColors(); + + bool getCreateTriggerContext() const; + void setCreateTriggerContext(bool value); + + protected: + void highlightBlock(const QString &text); + + private: + enum class TextBlockState + { + REGULAR = -1, // default, the -1 is default of QSyntaxHighlighter + BLOB, // x'blob' + STRING, // 'string' + COMMENT, // /* comment */ + ID_1, // [id] + ID_2, // "id" + ID_3 // `id` + }; + + struct Error + { + Error(int from, int to, bool limitedDamage = false); + + int from; + int to; + bool limitedDamage = false; // if it's just an invalid token, but parser dealt with it, mark only this token + }; + + struct DbObject + { + DbObject(int from, int to); + + int from; + int to; + }; + + void setupMapping(); + + /** + * @brief getPreviousStatePrefix Provides prefix for previous block's state. + * @param textBlockState Previous text block's state. + * @return Prefix string (if any) for lexer to provide proper tokens according to previous state. + */ + QString getPreviousStatePrefix(TextBlockState textBlockState); + + /** + * @brief handleToken Highlights token. + * @param token Token to handle. + * @param idxModifier Modifier for text highlighting in case of previous state defined by multi-character token. See getPreviousStatePrefix() for details. + * @return true if the token is being marked as invalid (syntax error). + */ + bool handleToken(TokenPtr token, qint32 idxModifier, int errorStart, TextBlockData* currBlockData, TextBlockData* previousBlockData); + + bool isError(int start, int lgt, bool* limitedDamage); + bool isValid(int start, int lgt); + + /** + * @brief markUncheckedErrors Marks text as being uncheck for possible errors. + * @param errorStart Start index of unchecked text. + * Unchecked text is all text after first error, becuase it could not be parser, therefore could not be checked. + */ + void markUncheckedErrors(int errorStart, int length); + void setStateForUnfinishedToken(TolerantTokenPtr tolerantToken); + + /** + * @brief applyErrorFormat Applies error format properties to given format. + * @param format Format to apply properties to. + * @param isError true if error was detected and error format needs to be applied. + * @param wasError true if there was an error already in previous token. + */ + void applyErrorFormat(QTextCharFormat& format, bool isError, bool wasError, Token::Type tokenType); + + /** + * @brief applyValidObjectFormat Applies valid database object format properties to given format. + * @param format Format to apply properties to. + * @param isValid true if we're about to mark valid database object + * @param qtextdisError true if error was detected and error format needs to be applied. + * @param wasError true if there was an error already in previous token. + */ + void applyValidObjectFormat(QTextCharFormat& format, bool isValid, bool isError, bool wasError); + + void handleParenthesis(TokenPtr token, TextBlockData* data); + + static const int regulartTextBlockState = static_cast(TextBlockState::REGULAR); + int sqliteVersion = 3; + QHash formats; + QHash tokenTypeMapping; + QList errors; + QList dbObjects; + bool objectLinksEnabled = false; + bool createTriggerContext = false; + + private slots: + void setupFormats(); +}; + +class GUI_API_EXPORT SqliteHighlighterPlugin : public BuiltInPlugin, public SyntaxHighlighterPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("SQL highlighter") + SQLITESTUDIO_PLUGIN_DESC("SQL (SQLite) syntax highlighter.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + QString getLanguageName() const; + QSyntaxHighlighter* createSyntaxHighlighter(QWidget* textEdit) const; +}; + +GUI_API_EXPORT int qHash(SqliteSyntaxHighlighter::State state); + +#endif // SQLITESYNTAXHIGHLIGHTER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlview.cpp b/SQLiteStudio3/guiSQLiteStudio/sqlview.cpp new file mode 100644 index 0000000..e65a60f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/sqlview.cpp @@ -0,0 +1,43 @@ +#include "sqlview.h" +#include "sqlitesyntaxhighlighter.h" +#include "uiconfig.h" + +SqlView::SqlView(QWidget *parent) : + QTextEdit(parent) +{ + highlighter = new SqliteSyntaxHighlighter(this->document()); + setFont(CFG_UI.Fonts.SqlEditor.get()); + connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant))); + setReadOnly(true); +} + +void SqlView::setSqliteVersion(int version) +{ + highlighter->setSqliteVersion(version); +} + +void SqlView::setTextBackgroundColor(int from, int to, const QColor& color) +{ + bool wasRo = false; + if (isReadOnly()) + { + wasRo = true; + setReadOnly(false); + } + + QTextCharFormat format; + format.setBackground(color); + + QTextCursor cur(document()); + cur.setPosition(from); + cur.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, to - from + 1); + cur.mergeCharFormat(format); + + if (wasRo) + setReadOnly(true); +} + +void SqlView::changeFont(const QVariant &font) +{ + setFont(font.value()); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/sqlview.h b/SQLiteStudio3/guiSQLiteStudio/sqlview.h new file mode 100644 index 0000000..7358a43 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/sqlview.h @@ -0,0 +1,25 @@ +#ifndef SQLVIEW_H +#define SQLVIEW_H + +#include "guiSQLiteStudio_global.h" +#include + +class SqliteSyntaxHighlighter; + +class GUI_API_EXPORT SqlView : public QTextEdit +{ + Q_OBJECT + public: + explicit SqlView(QWidget *parent = 0); + + void setSqliteVersion(int version); + void setTextBackgroundColor(int from, int to, const QColor& color); + + private: + SqliteSyntaxHighlighter* highlighter = nullptr; + + private slots: + void changeFont(const QVariant& font); +}; + +#endif // SQLVIEW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/statusfield.cpp b/SQLiteStudio3/guiSQLiteStudio/statusfield.cpp new file mode 100644 index 0000000..5de85d7 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/statusfield.cpp @@ -0,0 +1,218 @@ +#include "statusfield.h" +#include "ui_statusfield.h" +#include "mainwindow.h" +#include "uiconfig.h" +#include "iconmanager.h" +#include "common/tablewidget.h" +#include "services/notifymanager.h" +#include +#include +#include +#include +#include +#include + +StatusField::StatusField(QWidget *parent) : + QDockWidget(parent), + ui(new Ui::StatusField) +{ + ui->setupUi(this); + setupMenu(); + ui->tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + ui->tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + + NotifyManager* nm = NotifyManager::getInstance(); + connect(nm, SIGNAL(notifyInfo(QString)), this, SLOT(info(QString))); + connect(nm, SIGNAL(notifyError(QString)), this, SLOT(error(QString))); + connect(nm, SIGNAL(notifyWarning(QString)), this, SLOT(warn(QString))); + connect(CFG_UI.Fonts.StatusField, SIGNAL(changed(QVariant)), this, SLOT(fontChanged(QVariant))); + + readRecentMessages(); +} + +bool StatusField::hasMessages() const +{ + return ui->tableWidget->rowCount() > 0; +} + +StatusField::~StatusField() +{ + delete ui; +} + +void StatusField::changeEvent(QEvent *e) +{ + QDockWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void StatusField::info(const QString &text) +{ + addEntry(ICONS.STATUS_INFO, text, CFG_UI.Colors.StatusFieldInfoFg.get()); +} + +void StatusField::warn(const QString &text) +{ + addEntry(ICONS.STATUS_WARNING, text, CFG_UI.Colors.StatusFieldWarnFg.get()); +} + +void StatusField::error(const QString &text) +{ + addEntry(ICONS.STATUS_ERROR, text, CFG_UI.Colors.StatusFieldErrorFg.get()); +} + +void StatusField::addEntry(const QIcon &icon, const QString &text, const QColor& color) +{ + int row = ui->tableWidget->rowCount(); + ui->tableWidget->setRowCount(row+1); + + if (row > itemCountLimit) + { + ui->tableWidget->removeRow(0); + row--; + } + + QList itemsCreated; + QTableWidgetItem* item = nullptr; + + item = new QTableWidgetItem(); + item->setIcon(icon); + ui->tableWidget->setItem(row, 0, item); + itemsCreated << item; + + QFont font = CFG_UI.Fonts.StatusField.get(); + + QString timeStr = "[" + QDateTime::currentDateTime().toString(timeStampFormat) + "]"; + item = new QTableWidgetItem(timeStr); + item->setForeground(QBrush(color)); + item->setFont(font); + ui->tableWidget->setItem(row, 1, item); + itemsCreated << item; + + item = new QTableWidgetItem(); + item->setForeground(QBrush(color)); + item->setFont(font); + ui->tableWidget->setItem(row, 2, item); + itemsCreated << item; + + static_qstring(colorTpl, "QLabel {color: %1}"); + // While QLabel does detect if the text is rich automatically, we don't want to use qlabel for plain text, + // because it's not wrapped correctly if the text is longer. + if (text.contains("<")) + { + QLabel* label = new QLabel(text); + QMargins margin = label->contentsMargins(); + margin.setLeft(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing)); + label->setContentsMargins(margin); + label->setFont(font); + label->setStyleSheet(colorTpl.arg(color.name())); + connect(label, SIGNAL(linkActivated(QString)), this, SIGNAL(linkActivated(QString))); + ui->tableWidget->setCellWidget(row, 2, label); + } + else + { + item->setText(text); + } + + setVisible(true); + + ui->tableWidget->scrollToBottom(); + + if (!noFlashing) + flashItems(itemsCreated, color); +} + +void StatusField::flashItems(const QList& items, const QColor& color) +{ + QColor alphaColor = color; + alphaColor.setAlpha(0); + + QColor finalColor = color; + finalColor.setAlpha(150); + + QVariantAnimation* anim = new QVariantAnimation(); + anim->setDuration(500); + anim->setEasingCurve(QEasingCurve::OutQuad); + anim->setStartValue(finalColor); + anim->setEndValue(alphaColor); + + itemAnimations << anim; + connect(anim, &QObject::destroyed, [this, anim]() {itemAnimations.removeOne(anim);}); + + connect(anim, &QVariantAnimation::valueChanged, [items](const QVariant& value) + { + for (QTableWidgetItem* item : items) + item->setBackground(value.value()); + }); + + anim->start(QAbstractAnimation::DeleteWhenStopped); +} + +void StatusField::setupMenu() +{ + menu = new QMenu(this); + + copyAction = new QAction(ICONS.ACT_COPY, tr("Copy"), ui->tableWidget); + copyAction->setShortcut(QKeySequence::Copy); + connect(copyAction, &QAction::triggered, ui->tableWidget, &TableWidget::copy); + menu->addAction(copyAction); + + menu->addSeparator(); + + clearAction = new QAction(ICONS.ACT_CLEAR, tr("Clear"), ui->tableWidget); + connect(clearAction, &QAction::triggered, this, &StatusField::reset); + menu->addAction(clearAction); + + connect(ui->tableWidget, &QWidget::customContextMenuRequested, this, &StatusField::customContextMenuRequested); +} + +void StatusField::readRecentMessages() +{ + noFlashing = true; + foreach (const QString& msg, NotifyManager::getInstance()->getRecentInfos()) + info(msg); + + foreach (const QString& msg, NotifyManager::getInstance()->getRecentWarnings()) + warn(msg); + + foreach (const QString& msg, NotifyManager::getInstance()->getRecentErrors()) + error(msg); + + noFlashing = false; +} + +void StatusField::customContextMenuRequested(const QPoint &pos) +{ + copyAction->setEnabled(ui->tableWidget->selectionModel()->selectedRows().size() > 0); + + menu->popup(ui->tableWidget->mapToGlobal(pos)); +} + +void StatusField::reset() +{ + for (QAbstractAnimation* anim : itemAnimations) + anim->stop(); + + itemAnimations.clear(); + ui->tableWidget->clear(); + ui->tableWidget->setRowCount(0); +} + +void StatusField::fontChanged(const QVariant& variant) +{ + QFont newFont = variant.value(); + QFont font; + for (int row = 0; row < ui->tableWidget->rowCount(); row++) + { + font = ui->tableWidget->item(row, 1)->font(); + font = newFont.resolve(font); + for (int col = 1; col <= 2; col++) + ui->tableWidget->item(row, col)->setFont(font); + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/statusfield.h b/SQLiteStudio3/guiSQLiteStudio/statusfield.h new file mode 100644 index 0000000..ac07f51 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/statusfield.h @@ -0,0 +1,58 @@ +#ifndef STATUSFIELD_H +#define STATUSFIELD_H + +#include "guiSQLiteStudio_global.h" +#include +#include + +class QMenu; +class QAbstractAnimation; +class QTableWidgetItem; + +namespace Ui { + class StatusField; +} + +class GUI_API_EXPORT StatusField : public QDockWidget +{ + Q_OBJECT + + public: + explicit StatusField(QWidget *parent = 0); + ~StatusField(); + + bool hasMessages() const; + + protected: + void changeEvent(QEvent *e); + + private: + void addEntry(const QIcon& icon, const QString& text, const QColor &color); + void flashItems(const QList& items, const QColor& color); + void setupMenu(); + void readRecentMessages(); + + Ui::StatusField *ui = nullptr; + QMenu* menu = nullptr; + QAction* copyAction = nullptr; + QAction* clearAction = nullptr; + QList itemAnimations; + bool noFlashing = false; + + static const int timeStampColumnWidth = 70; + static const int itemCountLimit = 30; + static constexpr const char* timeStampFormat = "hh:mm:ss"; + + private slots: + void customContextMenuRequested(const QPoint& pos); + void info(const QString& text); + void warn(const QString& text); + void error(const QString& text); + void reset(); + void fontChanged(const QVariant& variant); + + signals: + void linkActivated(const QString& link); +}; + +#endif // STATUSFIELD_H diff --git a/SQLiteStudio3/guiSQLiteStudio/statusfield.ui b/SQLiteStudio3/guiSQLiteStudio/statusfield.ui new file mode 100644 index 0000000..c36828c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/statusfield.ui @@ -0,0 +1,97 @@ + + + StatusField + + + + 0 + 0 + 708 + 106 + + + + Status + + + + + + + + 0 + 0 + + + + + 0 + 60 + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + Qt::NoPen + + + true + + + 3 + + + false + + + 24 + + + true + + + false + + + 18 + + + + + + + + + + + + TableWidget + QTableWidget +
    common/tablewidget.h
    +
    +
    + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h b/SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h new file mode 100644 index 0000000..15b6c2c --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/syntaxhighlighterplugin.h @@ -0,0 +1,17 @@ +#ifndef SYNTAXHIGHLIGHTERPLUGIN_H +#define SYNTAXHIGHLIGHTERPLUGIN_H + +#include "guiSQLiteStudio_global.h" +#include "plugins/plugin.h" + +class QWidget; +class QSyntaxHighlighter; + +class GUI_API_EXPORT SyntaxHighlighterPlugin : virtual public Plugin +{ + public: + virtual QString getLanguageName() const = 0; + virtual QSyntaxHighlighter* createSyntaxHighlighter(QWidget* textEdit) const = 0; +}; + +#endif // SYNTAXHIGHLIGHTERPLUGIN_H diff --git a/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp b/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp new file mode 100644 index 0000000..915ca9a --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp @@ -0,0 +1,299 @@ +#include "taskbar.h" +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TaskBar::TaskBar(const QString& title, QWidget *parent) : + QToolBar(title, parent), taskGroup(this) +{ + init(); +} + +TaskBar::TaskBar(QWidget* parent) : + QToolBar(parent), taskGroup(this) +{ + init(); +} + +QAction* TaskBar::addTask(const QIcon& icon, const QString& text) +{ + // A workaround for QAction button (or QToolBar itself) that takes over (and doesn't propagate) mousePressEvent. + QAction* action = QToolBar::addAction(icon, text); + tasks << action; + QToolButton* btn = getToolButton(action); + btn->setMaximumWidth(400); + if (!btn) + return action; + + taskGroup.addAction(action); + connect(btn, SIGNAL(pressed()), this, SLOT(mousePressed())); + return action; +} + +void TaskBar::removeTask(QAction* action) +{ + tasks.removeOne(action); + taskGroup.removeAction(action); + removeAction(action); +} + +QList TaskBar::getTasks() const +{ + return tasks; +} + +void TaskBar::init() +{ + setAcceptDrops(true); +} + +void TaskBar::mousePressed() +{ + dragStartPosition = mapFromGlobal(QCursor::pos()); + dragStartTask = actionAt(dragStartPosition); + if (dragStartTask) + dragStartTask->trigger(); +} + +int TaskBar::getActiveTaskIdx() +{ + QAction* checked = taskGroup.checkedAction(); + if (!checked) + { + // Looks like no tasks yet. + return -1; + } + + return tasks.indexOf(checked); +} + +void TaskBar::nextTask() +{ + int idx = getActiveTaskIdx() + 1; + if (tasks.size() <= idx) + return; + + tasks[idx]->trigger(); +} + +void TaskBar::prevTask() +{ + int idx = getActiveTaskIdx() - 1; + if (idx < 0) + return; + + tasks[idx]->trigger(); +} + +void TaskBar::initContextMenu(ExtActionContainer* mainWin) +{ + // MainWindow is passed as argument to this function, so it's not referenced with MAINWINDOW macro, + // because that macro causes MainWindow initialization and this caused endless loop. + taskMenu = new QMenu(this); + taskMenu->addAction(mainWin->getAction(MainWindow::CLOSE_WINDOW)); + taskMenu->addAction(mainWin->getAction(MainWindow::CLOSE_OTHER_WINDOWS)); + taskMenu->addAction(mainWin->getAction(MainWindow::CLOSE_ALL_WINDOWS)); + taskMenu->addSeparator(); + taskMenu->addAction(mainWin->getAction(MainWindow::RESTORE_WINDOW)); + taskMenu->addAction(mainWin->getAction(MainWindow::RENAME_WINDOW)); + + connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(taskBarMenuRequested(QPoint))); +} + +void TaskBar::taskBarMenuRequested(const QPoint &p) +{ + + QAction* task = actionAt(p); + bool taskClicked = (task != nullptr); + if (taskClicked) + task->trigger(); + + MAINWINDOW->updateWindowActions(); + taskMenu->popup(mapToGlobal(p)); +} + +QToolButton* TaskBar::getToolButton(QAction* action) +{ + return dynamic_cast(widgetForAction(action)); +} + +QAction* TaskBar::getNextClosestAction(const QPoint& position) +{ + QToolButton* btn = nullptr; + if (orientation() == Qt::Horizontal) + { + foreach (QAction* action, tasks) + { + btn = getToolButton(action); + if (btn && btn->x() >= position.x()) + return action; + } + } + else + { + foreach (QAction* action, tasks) + { + btn = getToolButton(action); + if (btn && btn->y() >= position.y()) + return action; + } + } + return nullptr; +} + +void TaskBar::mousePressEvent(QMouseEvent* event) +{ + QToolBar::mousePressEvent(event); + dragStartTask = nullptr; +} + +void TaskBar::mouseMoveEvent(QMouseEvent *event) +{ + if (!handleMouseMoveEvent(event)) + QToolBar::mouseMoveEvent(event); +} + +bool TaskBar::handleMouseMoveEvent(QMouseEvent* event) +{ + if (!(event->buttons() & Qt::LeftButton)) + return false; + + if (!dragStartTask) + return false; + + if ((event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance()) + return false; + + QDrag *drag = new QDrag(this); + drag->setMimeData(generateMimeData()); + + dragStartIndex = tasks.indexOf(dragStartTask); + return true; +} + +void TaskBar::dragEnterEvent(QDragEnterEvent *event) +{ + if (!event->mimeData()->hasFormat(mimeDataId)) + return; + + dragTaskTo(dragStartTask, event->pos()); + event->acceptProposedAction(); +} + +void TaskBar::dragMoveEvent(QDragMoveEvent* event) +{ + if (!event->mimeData()->hasFormat(mimeDataId)) + return; + + dragTaskTo(dragStartTask, event->pos()); + event->acceptProposedAction(); +} + +void TaskBar::dropEvent(QDropEvent *event) +{ + event->acceptProposedAction(); +} + +void TaskBar::dragTaskTo(QAction* task, const QPoint& position) +{ + int idx = getDropPositionIndex(task, position); + if (idx < 0) + return; + + dragTaskTo(task, idx); +} + +void TaskBar::dragTaskTo(QAction* task, int positionIndex) +{ + if (positionIndex < 0) + return; + + removeAction(task); + + if (positionIndex >= tasks.size()) + addAction(task); + else + insertAction(tasks.at(positionIndex), task); + + connect(getToolButton(task), SIGNAL(pressed()), this, SLOT(mousePressed())); + dragCurrentIndex = positionIndex; +} + +QMimeData* TaskBar::generateMimeData() +{ + QMimeData *mimeData = new QMimeData(); + mimeData->setData(mimeDataId, QByteArray()); + return mimeData; +} + +int TaskBar::getDropPositionIndex(QAction* task, const QPoint& position) +{ + QAction* action = actionAt(position); + if (!action) + action = getNextClosestAction(position); + + if (!action) + return tasks.size(); // We moved completly out of actions range, report last possible position. + + if (action == task) + return -1; + + int newIdx = tasks.indexOf(action); + + QToolButton* btn = getToolButton(action); + int actionBeginPos; + int actionEndPos; + int newPos; + if (orientation() == Qt::Horizontal) + { + actionBeginPos = btn->x(); + actionEndPos = btn->x() + btn->width(); + newPos = position.x(); + } + else + { + actionBeginPos = btn->y(); + actionEndPos = btn->y() + btn->height(); + newPos = position.y(); + } + + if (dragCurrentIndex <= newIdx) + { + // D&D from left to right + if (newPos >= actionBeginPos) + return newIdx + 1; + else + return newIdx; + + } + else + { + // D&D from right to left + if (newPos <= actionEndPos) + return newIdx; + else + return newIdx + 1; + } + + return -1; // This also should never happen. All cases should be covered above. But just in case. +} + +bool TaskBar::isEmpty() +{ + return tasks.isEmpty(); +} + +int TaskBar::count() +{ + return tasks.count(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/taskbar.h b/SQLiteStudio3/guiSQLiteStudio/taskbar.h new file mode 100644 index 0000000..e858f7b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/taskbar.h @@ -0,0 +1,73 @@ +#ifndef TASKBAR_H +#define TASKBAR_H + +#include "guiSQLiteStudio_global.h" +#include +#include + +class QMimeData; +class QToolButton; +class QRubberBand; +class ExtActionContainer; + +// TODO enclose task<->mdiWindow relation inside a task class and make it managed by taskbar, not by mdiarea +class GUI_API_EXPORT TaskBar : public QToolBar +{ + Q_OBJECT + public: + TaskBar(const QString& title, QWidget *parent = 0); + explicit TaskBar(QWidget *parent = 0); + + QAction* addTask(const QIcon& icon, const QString& text); + void removeTask(QAction* action); + QList getTasks() const; + bool isEmpty(); + int count(); + + protected: + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void dragEnterEvent(QDragEnterEvent* event); + void dragMoveEvent(QDragMoveEvent* event); + void dropEvent(QDropEvent* event); + + private: + void init(); + bool handleMouseMoveEvent(QMouseEvent* event); + QToolButton* getToolButton(QAction* action); + QAction* getNextClosestAction(const QPoint& position); + void dragTaskTo(QAction* task, const QPoint& position); + void dragTaskTo(QAction* task, int positionIndex); + QAction* getDragTask(const QMimeData* data); + QMimeData* generateMimeData(); + int getActiveTaskIdx(); + + constexpr static const char* mimeDataId = "application/x-sqlitestudio-taskbar-task"; + + /** + * @brief getDropPositionIndex + * @param task + * @param position + * @return Index of action in actions() that drag should be inserting dropped item just before, or -1 to indicate "at the end". + */ + int getDropPositionIndex(QAction* task, const QPoint& position); + + QActionGroup taskGroup; + QList tasks; + QAction* dragStartTask = nullptr; + QPoint dragStartPosition; + int dragStartIndex; + int dragCurrentIndex; + QMenu* taskMenu = nullptr; + + public slots: + void nextTask(); + void prevTask(); + void initContextMenu(ExtActionContainer *mainWin); + + private slots: + void taskBarMenuRequested(const QPoint& p); + void mousePressed(); +}; + +#endif // TASKBAR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp b/SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp new file mode 100644 index 0000000..c2dfa8f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiconfig.cpp @@ -0,0 +1,68 @@ +#include "uiconfig.h" +#include +#include +#include +#include +#include + +namespace Cfg +{ + QVariant getStyleDefaultValue() + { + return QApplication::style()->objectName(); + } + + QVariant getDefaultTextEditorFont() + { + QPlainTextEdit monoEdit; + QFont font = monoEdit.document()->defaultFont(); +#ifdef Q_OS_MACX + font.setFamily("Courier New"); +#elif defined(Q_OS_WIN32) + font.setFamily("Consolas"); +#else + font.setFamily("DejaVu Sans Mono"); +#endif + return QVariant::fromValue(font); + } + + QVariant getDefaultItemViewFont() + { + QStandardItem it; + return it.font(); + } + + QVariant getDefaultDbTreeLabelFont() + { + QFont font = getDefaultItemViewFont().value(); +#ifdef Q_OS_WIN32 + font.setPointSize(font.pointSize() - 1); +#else + font.setPointSize(font.pointSize() - 2); +#endif + return font; + } + +} + +CFG_DEFINE(Ui) + +void setFileDialogInitPathByFile(const QString& filePath) +{ + if (filePath.isNull()) + return; + + QDir newDir(filePath); + newDir.cdUp(); + setFileDialogInitPath(newDir.absolutePath()); +} + +void setFileDialogInitPath(const QString& path) +{ + CFG_UI.General.FileDialogLastPath.set(path); +} + +QString getFileDialogInitPath() +{ + return CFG_UI.General.FileDialogLastPath.get(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/uiconfig.h b/SQLiteStudio3/guiSQLiteStudio/uiconfig.h new file mode 100644 index 0000000..7b645e1 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiconfig.h @@ -0,0 +1,90 @@ +#ifndef UICONFIG_H +#define UICONFIG_H + +#include "guiSQLiteStudio_global.h" +#include "config_builder.h" +#include +#include +#include + +namespace Cfg +{ + GUI_API_EXPORT QVariant getStyleDefaultValue(); + GUI_API_EXPORT QVariant getDefaultTextEditorFont(); + GUI_API_EXPORT QVariant getDefaultItemViewFont(); + GUI_API_EXPORT QVariant getDefaultDbTreeLabelFont(); + typedef QHash Session; + typedef QHash DataEditorsOrder; +} + +CFG_CATEGORIES(Ui, + CFG_CATEGORY(Fonts, + CFG_ENTRY(QFont, SqlEditor, &Cfg::getDefaultTextEditorFont) + CFG_ENTRY(QFont, DataView, &Cfg::getDefaultItemViewFont) + CFG_ENTRY(QFont, DbTree, &Cfg::getDefaultItemViewFont) + CFG_ENTRY(QFont, DbTreeLabel, &Cfg::getDefaultDbTreeLabelFont) + CFG_ENTRY(QFont, StatusField, &Cfg::getDefaultItemViewFont) + ) + + CFG_CATEGORY(Colors, + CFG_ENTRY(QColor, SqlEditorParenthesisBg, Qt::green) + CFG_ENTRY(QColor, SqlEditorCurrentLineBg, QColor(Qt::cyan).lighter(190)) + CFG_ENTRY(QColor, SqlEditorLineNumAreaBg, QColor(Qt::lightGray).lighter(120)) + CFG_ENTRY(QColor, SqlEditorValidObject, Qt::blue) + CFG_ENTRY(QColor, SqlEditorForeground, Qt::black) + CFG_ENTRY(QColor, SqlEditorStringFg, Qt::darkGreen) + CFG_ENTRY(QColor, SqlEditorKeywordFg, Qt::black) + CFG_ENTRY(QColor, SqlEditorBindParamFg, Qt::darkMagenta) + CFG_ENTRY(QColor, SqlEditorBlobFg, Qt::darkCyan) + CFG_ENTRY(QColor, SqlEditorCommentFg, Qt::darkGray) + CFG_ENTRY(QColor, SqlEditorNumberFg, Qt::darkBlue) + CFG_ENTRY(QColor, DataUncommitedError, Qt::red) + CFG_ENTRY(QColor, DataUncommited, Qt::blue) + CFG_ENTRY(QColor, DataNullFg, Qt::gray) + CFG_ENTRY(QColor, DataDeletedBg, Qt::gray) + CFG_ENTRY(QColor, DbTreeLabelsFg, Qt::blue) + CFG_ENTRY(QColor, StatusFieldInfoFg, Qt::darkBlue) + CFG_ENTRY(QColor, StatusFieldWarnFg, Qt::black) + CFG_ENTRY(QColor, StatusFieldErrorFg, Qt::red) + CFG_ENTRY(QColor, JavaScriptFg, "#000000") + CFG_ENTRY(QColor, JavaScriptComment, "#808080") + CFG_ENTRY(QColor, JavaScriptNumber, "#008000") + CFG_ENTRY(QColor, JavaScriptString, "#800000") + CFG_ENTRY(QColor, JavaScriptOperator, "#808000") + CFG_ENTRY(QColor, JavaScriptIdentifier, "#000020") + CFG_ENTRY(QColor, JavaScriptKeyword, "#000080") + CFG_ENTRY(QColor, JavaScriptBuiltIn, "#008080") + CFG_ENTRY(QColor, JavaScriptMarker, "#ffff00") + ) + + CFG_CATEGORY(General, + CFG_ENTRY(QString, DataViewTabs, QString()) + CFG_ENTRY(QString, SqlEditorTabs, QString()) + CFG_ENTRY(QString, SqlEditorDbListOrder, "LikeDbTree") + CFG_ENTRY(bool, ExpandTables, true) + CFG_ENTRY(bool, ExpandViews, true) + CFG_ENTRY(bool, SortObjects, true) + CFG_ENTRY(bool, SortColumns, false) + CFG_ENTRY(bool, ExecuteCurrentQueryOnly, true) + CFG_ENTRY(bool, ShowSystemObjects, false) + CFG_ENTRY(bool, ShowDbTreeLabels, true) // any labels at all + CFG_ENTRY(bool, ShowRegularTableLabels, false) + CFG_ENTRY(bool, ShowVirtualTableLabels, true) + CFG_ENTRY(int, NumberOfRowsPerPage, 1000) + CFG_ENTRY(QString, Style, &Cfg::getStyleDefaultValue) + CFG_ENTRY(Cfg::Session, Session, Cfg::Session()) + CFG_ENTRY(bool, DontShowDdlPreview, false) + CFG_ENTRY(bool, OpenTablesOnData, false) + CFG_ENTRY(bool, OpenViewsOnData, false) + CFG_ENTRY(Cfg::DataEditorsOrder, DataEditorsOrder, Cfg::DataEditorsOrder()) + CFG_ENTRY(QString, FileDialogLastPath, QString()) + ) +) + +QString getFileDialogInitPath(); +void setFileDialogInitPath(const QString& path); +void setFileDialogInitPathByFile(const QString& filePath); + +#define CFG_UI CFG_INSTANCE(Ui) + +#endif // UICONFIG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp b/SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp new file mode 100644 index 0000000..f72eb35 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uicustomicon.cpp @@ -0,0 +1,30 @@ +#include "uicustomicon.h" +#include "iconmanager.h" +#include +#include + +#define TRY_ICON_WITH(Type, Widget, Method, Icon) \ + if (dynamic_cast(Widget))\ + {\ + dynamic_cast(Widget)->Method(Icon);\ + return;\ + } + +UiCustomIcon::UiCustomIcon() +{ +} + +const char* UiCustomIcon::getPropertyName() const +{ + return "customIcon"; +} + +void UiCustomIcon::handle(QWidget* widget, const QVariant& value) +{ + QString iconName = value.toString(); + QIcon* icon = ICONMANAGER->getIcon(iconName); + if (!icon) + return; + + TRY_ICON_WITH(QAbstractButton, widget, setIcon, *icon); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/uicustomicon.h b/SQLiteStudio3/guiSQLiteStudio/uicustomicon.h new file mode 100644 index 0000000..8332970 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uicustomicon.h @@ -0,0 +1,16 @@ +#ifndef UICUSTOMICON_H +#define UICUSTOMICON_H + +#include "guiSQLiteStudio_global.h" +#include "uiloaderpropertyhandler.h" + +class GUI_API_EXPORT UiCustomIcon : public UiLoaderPropertyHandler +{ + public: + UiCustomIcon(); + + const char* getPropertyName() const; + void handle(QWidget* widget, const QVariant& value); +}; + +#endif // UICUSTOMICON_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp b/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp new file mode 100644 index 0000000..9504bda --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uidebug.cpp @@ -0,0 +1,116 @@ +#include "uidebug.h" +#include "common/unused.h" +#include "qio.h" +#include "debugconsole.h" +#include "common/global.h" +#include + +DebugConsole* sqliteStudioUiDebugConsole = nullptr; +MsgHandlerThreadProxy* msgHandlerThreadProxy = nullptr; +bool UI_DEBUG_ENABLED = false; +bool UI_DEBUG_CONSOLE = true; + +void uiMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + if (!UI_DEBUG_ENABLED) + return; + + UNUSED(context); + + static const QString dbgMsg = QStringLiteral("[%1] DEBUG: %2"); + static const QString wrnMsg = QStringLiteral("[%1] WARNING: %2"); + static const QString criMsg = QStringLiteral("[%1] CRITICAL: %2"); + static const QString fatMsg = QStringLiteral("[%1] FATAL: %2"); + + QString time = QTime::currentTime().toString("HH:mm:ss.zzz"); + switch (type) { + case QtDebugMsg: + msgHandlerThreadProxy->debug(dbgMsg.arg(time, msg)); + break; + case QtWarningMsg: + msgHandlerThreadProxy->warn(wrnMsg.arg(time, msg)); + break; + case QtCriticalMsg: + msgHandlerThreadProxy->critical(criMsg.arg(time, msg)); + break; + case QtFatalMsg: + msgHandlerThreadProxy->fatal(fatMsg.arg(time, msg)); + abort(); + } +} + +void setUiDebug(bool enabled, bool useUiConsole) +{ + UI_DEBUG_ENABLED = enabled; + UI_DEBUG_CONSOLE = useUiConsole; + safe_delete(msgHandlerThreadProxy); + safe_delete(sqliteStudioUiDebugConsole); + if (enabled) + { + if (useUiConsole) + sqliteStudioUiDebugConsole = new DebugConsole(); + + msgHandlerThreadProxy = new MsgHandlerThreadProxy(); + } +} + +void showUiDebugConsole() +{ + if (sqliteStudioUiDebugConsole) + sqliteStudioUiDebugConsole->show(); +} + +bool isDebugEnabled() +{ + return UI_DEBUG_ENABLED; +} + +bool isDebugConsoleEnabled() +{ + return UI_DEBUG_CONSOLE; +} + +MsgHandlerThreadProxy::MsgHandlerThreadProxy(QObject *parent) : + QObject(parent) +{ + if (sqliteStudioUiDebugConsole) + { + connect(this, SIGNAL(debugRequested(QString)), sqliteStudioUiDebugConsole, SLOT(debug(QString))); + connect(this, SIGNAL(warnRequested(QString)), sqliteStudioUiDebugConsole, SLOT(warning(QString))); + connect(this, SIGNAL(criticalRequested(QString)), sqliteStudioUiDebugConsole, SLOT(critical(QString))); + connect(this, SIGNAL(fatalRequested(QString)), sqliteStudioUiDebugConsole, SLOT(fatal(QString))); + } + else + { + connect(this, SIGNAL(debugRequested(QString)), this, SLOT(print(QString))); + connect(this, SIGNAL(warnRequested(QString)), this, SLOT(print(QString))); + connect(this, SIGNAL(criticalRequested(QString)), this, SLOT(print(QString))); + connect(this, SIGNAL(fatalRequested(QString)), this, SLOT(print(QString))); + } +} + +void MsgHandlerThreadProxy::debug(const QString &msg) +{ + emit debugRequested(msg); +} + +void MsgHandlerThreadProxy::warn(const QString &msg) +{ + emit warnRequested(msg); +} + +void MsgHandlerThreadProxy::critical(const QString &msg) +{ + emit criticalRequested(msg); +} + +void MsgHandlerThreadProxy::fatal(const QString &msg) +{ + emit fatalRequested(msg); +} + +void MsgHandlerThreadProxy::print(const QString& txt) +{ + qOut << txt << "\n"; + qOut.flush(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/uidebug.h b/SQLiteStudio3/guiSQLiteStudio/uidebug.h new file mode 100644 index 0000000..d1b04b7 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uidebug.h @@ -0,0 +1,36 @@ +#ifndef UIDEBUG_H +#define UIDEBUG_H + +#include "guiSQLiteStudio_global.h" +#include + +class GUI_API_EXPORT MsgHandlerThreadProxy : public QObject +{ + Q_OBJECT + + public: + explicit MsgHandlerThreadProxy(QObject* parent = 0); + + public slots: + void debug(const QString& msg); + void warn(const QString& msg); + void critical(const QString& msg); + void fatal(const QString& msg); + + signals: + void debugRequested(const QString& msg); + void warnRequested(const QString& msg); + void criticalRequested(const QString& msg); + void fatalRequested(const QString& msg); + + private slots: + void print(const QString& txt); +}; + +GUI_API_EXPORT void uiMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); +GUI_API_EXPORT void setUiDebug(bool enabled, bool useUiConsole = true); +GUI_API_EXPORT void showUiDebugConsole(); +GUI_API_EXPORT bool isDebugEnabled(); +GUI_API_EXPORT bool isDebugConsoleEnabled(); + +#endif // UIDEBUG_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uiloader.cpp b/SQLiteStudio3/guiSQLiteStudio/uiloader.cpp new file mode 100644 index 0000000..cc02b16 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiloader.cpp @@ -0,0 +1,95 @@ +#include "uiloader.h" +#include "common/unused.h" +#include "uiloaderpropertyhandler.h" +#include "uiscriptingcombo.h" +#include "uiscriptingedit.h" +#include "uicustomicon.h" +#include "uiurlbutton.h" +#include "sqlview.h" +#include "common/configradiobutton.h" +#include "common/configcombobox.h" +#include "common/fileedit.h" +#include "common/colorbutton.h" +#include +#include +#include +#include + +#define REGISTER_WIDGET(Class) \ + registerWidgetClass(#Class, [](QWidget* parent, const QString& name) -> QWidget*\ + {\ + Class* w = new Class(parent);\ + w->setObjectName(name);\ + return w;\ + }) + +UiLoader::UiLoader(QObject *parent) : + QUiLoader(parent) +{ + registerPropertyHandler(new UiScriptingCombo()); + registerPropertyHandler(new UiScriptingEdit()); + registerPropertyHandler(new UiCustomIcon()); + registerPropertyHandler(new UiUrlButton()); + + REGISTER_WIDGET(ConfigRadioButton); + REGISTER_WIDGET(ConfigComboBox); + REGISTER_WIDGET(FileEdit); + REGISTER_WIDGET(ColorButton); + REGISTER_WIDGET(SqlView); +} + +QWidget* UiLoader::createWidget(const QString& className, QWidget* parent, const QString& name) +{ + QWidget* w = nullptr; + if (registeredClasses.contains(className)) + w = registeredClasses[className](parent, name); + else + w = QUiLoader::createWidget(className, parent, name); + + return w; +} + +void UiLoader::registerWidgetClass(const QString& className, FactoryFunction factoryFunction) +{ + registeredClasses[className] = factoryFunction; +} + +void UiLoader::handlePropertiesRecursively(QWidget* widget) +{ + if (widget->dynamicPropertyNames().size() > 0) + handleProperties(widget); + + for (QWidget* w : widget->findChildren()) + handleProperties(w); +} + +void UiLoader::handleProperties(QWidget* widget) +{ + QVariant propValue; + for (UiLoaderPropertyHandler* handler : propertyHandlers) + { + propValue = widget->property(handler->getPropertyName()); + if (propValue.isValid()) + handler->handle(widget, propValue); + } +} + +QWidget* UiLoader::load(const QString& path) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + { + qCritical() << "FormManager was unable to open ui file:" << path; + return nullptr; + } + + QWidget* w = QUiLoader::load(&file, nullptr); + handlePropertiesRecursively(w); + return w; +} + +void UiLoader::registerPropertyHandler(UiLoaderPropertyHandler* handler) +{ + propertyHandlers << handler; +} + diff --git a/SQLiteStudio3/guiSQLiteStudio/uiloader.h b/SQLiteStudio3/guiSQLiteStudio/uiloader.h new file mode 100644 index 0000000..5d16bcb --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiloader.h @@ -0,0 +1,33 @@ +#ifndef UILOADER_H +#define UILOADER_H + +#include "guiSQLiteStudio_global.h" +#include +#include +#include +#include + +class UiLoaderPropertyHandler; + +class GUI_API_EXPORT UiLoader : public QUiLoader +{ + Q_OBJECT + public: + typedef std::function FactoryFunction; + + explicit UiLoader(QObject *parent = 0); + + QWidget* createWidget(const QString & className, QWidget * parent = 0, const QString & name = QString()); + void registerWidgetClass(const QString& className, FactoryFunction factoryFunction); + void registerPropertyHandler(UiLoaderPropertyHandler* handler); + QWidget* load(const QString& path); + + private: + void handlePropertiesRecursively(QWidget* widget); + void handleProperties(QWidget* widget); + + QHash registeredClasses; + QList propertyHandlers; +}; + +#endif // UILOADER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h b/SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h new file mode 100644 index 0000000..506d588 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiloaderpropertyhandler.h @@ -0,0 +1,16 @@ +#ifndef UILOADERPROPERTYHANDLER_H +#define UILOADERPROPERTYHANDLER_H + +#include "guiSQLiteStudio_global.h" +#include + +class QWidget; + +class GUI_API_EXPORT UiLoaderPropertyHandler +{ + public: + virtual const char* getPropertyName() const = 0; + virtual void handle(QWidget* widget, const QVariant& value) = 0; +}; + +#endif // UILOADERPROPERTYHANDLER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp b/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp new file mode 100644 index 0000000..b203873 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.cpp @@ -0,0 +1,26 @@ +#include "uiscriptingcombo.h" +#include "services/pluginmanager.h" +#include "plugins/scriptingplugin.h" +#include + +UiScriptingCombo::UiScriptingCombo() +{ +} + +const char* UiScriptingCombo::getPropertyName() const +{ + return "ScriptingLangCombo"; +} + +void UiScriptingCombo::handle(QWidget* widget, const QVariant& value) +{ + QComboBox* cb = dynamic_cast(widget); + if (!cb) + return; + + if (!value.toBool()) + return; + + for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins()) + cb->addItem(plugin->getLanguage()); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h b/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h new file mode 100644 index 0000000..ad9430f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiscriptingcombo.h @@ -0,0 +1,16 @@ +#ifndef UISCRIPTINGCOMBO_H +#define UISCRIPTINGCOMBO_H + +#include "guiSQLiteStudio_global.h" +#include "uiloaderpropertyhandler.h" + +class GUI_API_EXPORT UiScriptingCombo : public UiLoaderPropertyHandler +{ + public: + UiScriptingCombo(); + + const char* getPropertyName() const; + void handle(QWidget* widget, const QVariant& value); +}; + +#endif // UISCRIPTINGCOMBO_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp b/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp new file mode 100644 index 0000000..329af53 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.cpp @@ -0,0 +1,75 @@ +#include "uiscriptingedit.h" +#include "common/unused.h" +#include "services/pluginmanager.h" +#include "syntaxhighlighterplugin.h" +#include "pluginservicebase.h" +#include +#include +#include +#include + +UiScriptingEdit::UiScriptingEdit() +{ +} + +const char* UiScriptingEdit::getPropertyName() const +{ + return "scriptingEdit"; +} + +void UiScriptingEdit::handle(QWidget* widget, const QVariant& value) +{ + if (!value.toBool()) + return; + + new EditUpdater(widget); // widget becomes its parent and owns it +} + +UiScriptingEdit::EditUpdater::EditUpdater(QWidget* widget) : + QObject(widget), watchedWidget(widget) +{ + widget->installEventFilter(this); +} + +bool UiScriptingEdit::EditUpdater::eventFilter(QObject* obj, QEvent* e) +{ + UNUSED(obj); + if (changingHighlighter) + return false; + + if (e->type() != QEvent::DynamicPropertyChange) + return false; + + if (dynamic_cast(e)->propertyName() != PluginServiceBase::LANG_PROPERTY_NAME) + return false; + + QVariant prop = watchedWidget->property(PluginServiceBase::LANG_PROPERTY_NAME); + installNewHighlighter(prop); + + return false; +} + +void UiScriptingEdit::EditUpdater::installNewHighlighter(const QVariant& prop) +{ + QString lang = prop.toString(); + if (lang == currentLang) + return; + + // When highlighter is deleted, it causes textChanged() signal and so this method is called recurrently. + // To avoid inifinite recursion, the changingHighlighter is used to ignore property changes during deletion + // of the highlighter. + changingHighlighter = true; + safe_delete(currentHighlighter); + currentLang = QString(); + changingHighlighter = false; + + for (SyntaxHighlighterPlugin* plugin : PLUGINS->getLoadedPlugins()) + { + if (plugin->getLanguageName() != lang) + continue; + + currentHighlighter = plugin->createSyntaxHighlighter(watchedWidget); + currentLang = lang; + break; + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h b/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h new file mode 100644 index 0000000..fdd2df8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiscriptingedit.h @@ -0,0 +1,37 @@ +#ifndef UISCRIPTINGEDIT_H +#define UISCRIPTINGEDIT_H + +#include "uiloaderpropertyhandler.h" +#include "common/global.h" +#include "guiSQLiteStudio_global.h" +#include + +class QSyntaxHighlighter; + +class GUI_API_EXPORT UiScriptingEdit : public UiLoaderPropertyHandler +{ + public: + UiScriptingEdit(); + + const char* getPropertyName() const; + void handle(QWidget* widget, const QVariant& value); + + private: + class EditUpdater : public QObject + { + public: + EditUpdater(QWidget* widget); + + bool eventFilter(QObject* obj, QEvent* e); + + private: + void installNewHighlighter(const QVariant& prop); + + QWidget* watchedWidget = nullptr; + QString currentLang; + QSyntaxHighlighter* currentHighlighter = nullptr; + bool changingHighlighter = false; + }; +}; + +#endif // UISCRIPTINGEDIT_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp b/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp new file mode 100644 index 0000000..dcc3dc8 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.cpp @@ -0,0 +1,27 @@ +#include "uiurlbutton.h" +#include +#include +#include + +UiUrlButton::UiUrlButton() +{ +} + + +const char* UiUrlButton::getPropertyName() const +{ + return "openUrl"; +} + +void UiUrlButton::handle(QWidget* widget, const QVariant& value) +{ + QAbstractButton* btn = dynamic_cast(widget); + QString url = value.toString(); + if (btn) + { + QObject::connect(btn, &QAbstractButton::clicked, [url](bool) + { + QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode)); + }); + } +} diff --git a/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h b/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h new file mode 100644 index 0000000..72438ad --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiurlbutton.h @@ -0,0 +1,16 @@ +#ifndef UIURLBUTTON_H +#define UIURLBUTTON_H + +#include "guiSQLiteStudio_global.h" +#include "uiloaderpropertyhandler.h" + +class GUI_API_EXPORT UiUrlButton : public UiLoaderPropertyHandler +{ + public: + UiUrlButton(); + + const char* getPropertyName() const; + void handle(QWidget* widget, const QVariant& value); +}; + +#endif // UIURLBUTTON_H diff --git a/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp b/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp new file mode 100644 index 0000000..182cdb2 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp @@ -0,0 +1,123 @@ +#include "uiutils.h" +#include "services/config.h" +#include "common/widgetstateindicator.h" +#include "common/utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const QStringList pageSizes = { + "A4", "B5", "Letter", "Legal", "Executive", "A0", "A1", "A2", "A3", "A5", "A6", "A7", "A8", "A9", "B0", "B1", + "B10", "B2", "B3", "B4", "B6", "B7", "B8", "B9", "C5E", "Comm10E", "DLE", "Folio", "Ledger", "Tabloid", "Custom" +}; + +const QStringList pageSizesWithDimensions; + +QString getDbPath(const QString &startWith) +{ + QString dir = startWith; + if (dir.isNull()) + dir = CFG->get("dialogCache", "lastDbDir").toString(); + + QStringList filters; + filters += QObject::tr("All SQLite databases")+" (*.db *.sdb *.sqlite *.db3 *.s3db *.sqlite3 *.sl3 *.db2 *.s2db *.sqlite2 *.sl2)"; + filters += "SQLite3 (*.db3 *.s3db *.sqlite3 *.sl3)"; + filters += "SQLite2 (*.db2 *.s2db *.sqlite2 *.sl2)"; + filters += QObject::tr("All files")+" (*)"; + QString filter = filters.join(";;"); + + QString path = QFileDialog::getSaveFileName(0, QObject::tr("Database file"), dir, filter, &filters[0], QFileDialog::DontConfirmOverwrite); + return path; +} + +void setValidState(QWidget *widget, bool valid, const QString& message) +{ + INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::ERROR); + INDICATOR(widget)->setVisible(!valid, valid ? QString() : message); +} + +void setValidStateWihtTooltip(QWidget* widget, const QString& tooltip, bool valid, const QString& message) +{ + if (!valid) + { + INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::ERROR); + INDICATOR(widget)->setVisible(true, message); + } + else + { + INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::HINT); + INDICATOR(widget)->setVisible(widget->isEnabled(), tooltip); + } +} + +void setValidStateWarning(QWidget* widget, const QString& warning) +{ + INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::WARNING); + INDICATOR(widget)->setVisible(widget->isEnabled(), warning); +} + +void setValidStateInfo(QWidget* widget, const QString& info) +{ + INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::INFO); + INDICATOR(widget)->setVisible(widget->isEnabled(), info); +} + +void setValidStateTooltip(QWidget* widget, const QString& tip) +{ + INDICATOR(widget)->setMode(WidgetStateIndicator::Mode::HINT); + INDICATOR(widget)->setVisible(widget->isEnabled(), tip); +} + +QString convertPageSize(QPagedPaintDevice::PageSize size) +{ + const int pageSizesSize = pageSizes.size(); + + int idx = static_cast(size); + if (idx < 0 || idx >= pageSizesSize) + { + qDebug() << "Asked to convertPageSize() with page side enum value out of range:" << idx; + return QString::null; + } + + return pageSizes[idx]; +} + +QPagedPaintDevice::PageSize convertPageSize(const QString& size) +{ + return static_cast(indexOf(pageSizes, size, Qt::CaseInsensitive)); +} + +const QStringList& getAllPageSizes() +{ + return pageSizes; +} + +QPixmap addOpacity(const QPixmap& input, float opacity) +{ + QPixmap output(input.size()); + output.fill(Qt::transparent); + QPainter p(&output); + p.setOpacity(opacity); + p.drawPixmap(0, 0, input); + p.end(); + return output; +} + +void limitDialogWidth(QDialog* dialog) +{ + dialog->setMaximumWidth(QApplication::desktop()->availableGeometry().width()); +} + +void fixTextCursorSelectedText(QString& text) +{ + text.replace("\u2029", "\n"); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/uiutils.h b/SQLiteStudio3/guiSQLiteStudio/uiutils.h new file mode 100644 index 0000000..b1c78f3 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/uiutils.h @@ -0,0 +1,23 @@ +#ifndef UIUTILS_H +#define UIUTILS_H + +#include "guiSQLiteStudio_global.h" +#include +#include + +class QWidget; + +GUI_API_EXPORT QString getDbPath(const QString& startWith = QString::null); +GUI_API_EXPORT void setValidState(QWidget* widget, bool valid, const QString& message = QString()); +GUI_API_EXPORT void setValidStateWihtTooltip(QWidget* widget, const QString& tooltip, bool valid, const QString& message = QString()); +GUI_API_EXPORT void setValidStateWarning(QWidget* widget, const QString& warning); +GUI_API_EXPORT void setValidStateInfo(QWidget* widget, const QString& info); +GUI_API_EXPORT void setValidStateTooltip(QWidget* widget, const QString& tip); +GUI_API_EXPORT const QStringList& getAllPageSizes(); +GUI_API_EXPORT QString convertPageSize(QPagedPaintDevice::PageSize size); +GUI_API_EXPORT QPagedPaintDevice::PageSize convertPageSize(const QString& size); +GUI_API_EXPORT QPixmap addOpacity(const QPixmap& input, float opacity); +GUI_API_EXPORT void limitDialogWidth(QDialog* dialog); +GUI_API_EXPORT void fixTextCursorSelectedText(QString& text); + +#endif // UIUTILS_H diff --git a/SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp b/SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp new file mode 100644 index 0000000..4602050 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/widgetresizer.cpp @@ -0,0 +1,137 @@ +#include "widgetresizer.h" +#include "common/unused.h" +#include +#include +#include + +WidgetResizer::WidgetResizer(QWidget *parent) : + QWidget(parent) +{ + init(); +} + +WidgetResizer::~WidgetResizer() +{ +} + +WidgetResizer::WidgetResizer(const Qt::Orientation& orientation, QWidget* parent) : + QWidget(parent), orientation(orientation) +{ + init(); +} + +void WidgetResizer::init() +{ + updateCursor(); + updateWidth(); + widgetMinimumSize = QSize(20, 20); +} + +Qt::Orientation WidgetResizer::getOrientation() const +{ + return orientation; +} + +void WidgetResizer::setOrientation(const Qt::Orientation& value) +{ + orientation = value; + updateCursor(); +} + +void WidgetResizer::updateCursor() +{ + switch (orientation) + { + case Qt::Horizontal: + setCursor(Qt::SplitHCursor); + break; + case Qt::Vertical: + setCursor(Qt::SplitVCursor); + break; + } +} + +void WidgetResizer::updateWidth() +{ + setMinimumSize(width, width); +} + +void WidgetResizer::mousePressEvent(QMouseEvent* event) +{ + UNUSED(event); + if (!widget) + return; + + dragStartPosition = QCursor::pos(); + dragStartSize = widget->size(); +} + +void WidgetResizer::mouseMoveEvent(QMouseEvent* event) +{ + UNUSED(event); + if (!widget) + return; + + switch (orientation) + { + case Qt::Horizontal: + handleHorizontalMove(QCursor::pos().x()); + break; + case Qt::Vertical: + handleVerticalMove(QCursor::pos().y()); + break; + } +} + +void WidgetResizer::handleHorizontalMove(int position) +{ + int newWidth = dragStartSize.width() + position - dragStartPosition.y(); + if (newWidth < widgetMinimumSize.width()) + return; + + widget->setFixedWidth(newWidth); +} + +void WidgetResizer::handleVerticalMove(int position) +{ + int newHeight = dragStartSize.height() + position - dragStartPosition.y(); + if (newHeight < widgetMinimumSize.height()) + return; + + widget->setFixedHeight(newHeight); +} + +int WidgetResizer::getWidth() const +{ + return width; +} + +void WidgetResizer::setWidth(int value) +{ + width = value; +} + +QWidget* WidgetResizer::getWidget() const +{ + return widget; +} + +void WidgetResizer::setWidget(QWidget* value) +{ + widget = value; +} + +QSize WidgetResizer::getWidgetMinimumSize() const +{ + return widgetMinimumSize; +} + +void WidgetResizer::setWidgetMinimumSize(const QSize& value) +{ + widgetMinimumSize = value; +} + +void WidgetResizer::setWidgetMinimumSize(int width, int height) +{ + widgetMinimumSize = QSize(width, height); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/widgetresizer.h b/SQLiteStudio3/guiSQLiteStudio/widgetresizer.h new file mode 100644 index 0000000..29e380b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/widgetresizer.h @@ -0,0 +1,47 @@ +#ifndef WIDGETRESIZER_H +#define WIDGETRESIZER_H + +#include "guiSQLiteStudio_global.h" +#include + +class GUI_API_EXPORT WidgetResizer : public QWidget +{ + Q_OBJECT + public: + explicit WidgetResizer(const Qt::Orientation& orientation, QWidget *parent = 0); + explicit WidgetResizer(QWidget *parent = 0); + ~WidgetResizer(); + + Qt::Orientation getOrientation() const; + void setOrientation(const Qt::Orientation& value); + + int getWidth() const; + void setWidth(int value); + + QWidget* getWidget() const; + void setWidget(QWidget* value); + + QSize getWidgetMinimumSize() const; + void setWidgetMinimumSize(const QSize& value); + void setWidgetMinimumSize(int width, int height); + + protected: + void mouseMoveEvent(QMouseEvent* event); + void mousePressEvent(QMouseEvent* event); + + private: + void init(); + void updateCursor(); + void updateWidth(); + void handleHorizontalMove(int position); + void handleVerticalMove(int position); + + Qt::Orientation orientation = Qt::Vertical; + int width = 4; + QWidget* widget = nullptr; + QPoint dragStartPosition; + QSize dragStartSize; + QSize widgetMinimumSize; +}; + +#endif // WIDGETRESIZER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp new file mode 100644 index 0000000..c92f6f4 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp @@ -0,0 +1,155 @@ +#include "bugreporthistorywindow.h" +#include "ui_bugreporthistorywindow.h" +#include "common/unused.h" +#include "services/config.h" +#include +#include + +CFG_KEYS_DEFINE(BugReportHistoryWindow) + +BugReportHistoryWindow::BugReportHistoryWindow(QWidget *parent) : + MdiChild(parent), + ui(new Ui::BugReportHistoryWindow) +{ + init(); +} + +BugReportHistoryWindow::~BugReportHistoryWindow() +{ + delete ui; +} + +bool BugReportHistoryWindow::restoreSessionNextTime() +{ + return false; +} + +QVariant BugReportHistoryWindow::saveSession() +{ + return QVariant(); +} + +bool BugReportHistoryWindow::restoreSession(const QVariant& sessionValue) +{ + UNUSED(sessionValue); + return false; +} + +Icon* BugReportHistoryWindow::getIconNameForMdiWindow() +{ + return ICONS.BUG_LIST; +} + +QString BugReportHistoryWindow::getTitleForMdiWindow() +{ + return tr("Reports history"); +} + +void BugReportHistoryWindow::createActions() +{ + createAction(CLEAR_HISTORY, ICONS.CLEAR_HISTORY, tr("Clear reports history"), this, SLOT(clearHistory()), ui->toolBar); + createAction(DELETE_SELECTED, ICONS.DELETE_ROW, tr("Delete selected entry"), this, SLOT(deleteSelected()), ui->toolBar); +} + +void BugReportHistoryWindow::setupDefShortcuts() +{ + setShortcutContext({ + DELETE_SELECTED + }, + Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(BugReportHistoryWindow, Action); +} + +QToolBar* BugReportHistoryWindow::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return ui->toolBar; +} + +void BugReportHistoryWindow::init() +{ + ui->setupUi(this); + initActions(); + + reload(); + connect(ui->reportsList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState())); + connect(CFG, SIGNAL(reportsHistoryRefreshNeeded()), this, SLOT(reload())); + + updateState(); +} + +void BugReportHistoryWindow::updateState() +{ + actionMap[DELETE_SELECTED]->setEnabled(ui->reportsList->selectedItems().size() > 0); +} + +void BugReportHistoryWindow::reload() +{ + static_qstring(urlTpl, "%2"); + QString invalidUrlTpl = tr("Invalid response from server."); + + QList entries = CFG->getReportHistory(); + ui->reportsList->clear(); + ui->reportsList->setRowCount(entries.size()); + + QTableWidgetItem* item = nullptr; + QLabel* urlLabel = nullptr; + int row = 0; + for (const Config::ReportHistoryEntryPtr& entry : entries) + { + item = new QTableWidgetItem((entry->isFeatureRequest ? ICONS.FEATURE_REQUEST : ICONS.BUG), entry->title); + item->setData(ENTRY_ID, entry->id); + ui->reportsList->setItem(row, 0, item); + + item = new QTableWidgetItem(QDateTime::fromTime_t(entry->timestamp).toString("yyyy-MM-dd HH:mm:ss")); + ui->reportsList->setItem(row, 1, item); + + if (entry->url.startsWith("http://")) + urlLabel = new QLabel(urlTpl.arg(entry->url, entry->url)); + else + urlLabel = new QLabel(invalidUrlTpl); + + urlLabel->setOpenExternalLinks(true); + ui->reportsList->setCellWidget(row, 2, urlLabel); + + row++; + } + + ui->reportsList->setHorizontalHeaderLabels({tr("Title"), tr("Reported at"), tr("URL")}); + ui->reportsList->resizeColumnsToContents(); +} + +void BugReportHistoryWindow::clearHistory() +{ + CFG->clearReportHistory(); +} + +void BugReportHistoryWindow::deleteSelected() +{ + QList items = ui->reportsList->selectedItems(); + if (items.size() == 0) + { + qDebug() << "Called BugReportHistoryWindow::deleteSelected(), but there's no row selected."; + return; + } + + int id = items.first()->data(ENTRY_ID).toInt(); + if (id == 0) + { + qDebug() << "Called BugReportHistoryWindow::deleteSelected(), but there's no ID in selected row."; + return; + } + + CFG->deleteReport(id); +} + +bool BugReportHistoryWindow::isUncommited() const +{ + return false; +} + +QString BugReportHistoryWindow::getQuitUncommitedConfirmMessage() const +{ + return QString(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h new file mode 100644 index 0000000..e582a48 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h @@ -0,0 +1,65 @@ +#ifndef BUGREPORTHISTORYWINDOW_H +#define BUGREPORTHISTORYWINDOW_H + +#include "mdichild.h" +#include + +namespace Ui { + class BugReportHistoryWindow; +} + +CFG_KEY_LIST(BugReportHistoryWindow, QObject::tr("Reports history window"), + CFG_KEY_ENTRY(DELETE_SELECTED, Qt::Key_Delete, QObject::tr("Delete selected entry")) +) + +class GUI_API_EXPORT BugReportHistoryWindow : public MdiChild +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + DELETE_SELECTED, + CLEAR_HISTORY + }; + + enum ToolBar + { + TOOLBAR + }; + + explicit BugReportHistoryWindow(QWidget *parent = 0); + ~BugReportHistoryWindow(); + + bool restoreSessionNextTime(); + bool isUncommited() const; + QString getQuitUncommitedConfirmMessage() const; + + protected: + QVariant saveSession(); + bool restoreSession(const QVariant &sessionValue); + Icon* getIconNameForMdiWindow(); + QString getTitleForMdiWindow(); + void createActions(); + void setupDefShortcuts(); + QToolBar* getToolBar(int toolbar) const; + + private: + enum UserRole + { + ENTRY_ID = Qt::UserRole + 1 + }; + + void init(); + + Ui::BugReportHistoryWindow *ui = nullptr; + + private slots: + void updateState(); + void reload(); + void clearHistory(); + void deleteSelected(); +}; + +#endif // BUGREPORTHISTORYWINDOW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui new file mode 100644 index 0000000..3218822 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui @@ -0,0 +1,55 @@ + + + BugReportHistoryWindow + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + Title + + + + + Reported at + + + + + URL + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp new file mode 100644 index 0000000..1d0594d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp @@ -0,0 +1,389 @@ +#include "collationseditor.h" +#include "ui_collationseditor.h" +#include "common/unused.h" +#include "selectabledbmodel.h" +#include "dbtree/dbtree.h" +#include "dbtree/dbtreemodel.h" +#include "collationseditormodel.h" +#include "common/utils.h" +#include "uiutils.h" +#include "services/pluginmanager.h" +#include "syntaxhighlighterplugin.h" +#include "plugins/scriptingplugin.h" +#include "uiconfig.h" +#include +#include + +CollationsEditor::CollationsEditor(QWidget *parent) : + MdiChild(parent), + ui(new Ui::CollationsEditor) +{ + init(); +} + +CollationsEditor::~CollationsEditor() +{ + delete ui; +} + +bool CollationsEditor::restoreSessionNextTime() +{ + return false; +} + +QVariant CollationsEditor::saveSession() +{ + return QVariant(); +} + +bool CollationsEditor::restoreSession(const QVariant& sessionValue) +{ + UNUSED(sessionValue); + return true; +} + +Icon* CollationsEditor::getIconNameForMdiWindow() +{ + return ICONS.CONSTRAINT_COLLATION; +} + +QString CollationsEditor::getTitleForMdiWindow() +{ + return tr("Collations editor"); +} + +void CollationsEditor::createActions() +{ + createAction(COMMIT, ICONS.COMMIT, tr("Commit all collation changes"), this, SLOT(commit()), ui->toolbar); + createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback all collation changes"), this, SLOT(rollback()), ui->toolbar); + ui->toolbar->addSeparator(); + createAction(ADD, ICONS.NEW_COLLATION, tr("Create new collation"), this, SLOT(newCollation()), ui->toolbar); + createAction(DELETE, ICONS.DELETE_COLLATION, tr("Delete selected collation"), this, SLOT(deleteCollation()), ui->toolbar); + ui->toolbar->addSeparator(); + createAction(HELP, ICONS.HELP, tr("Editing collations manual"), this, SLOT(help()), ui->toolbar); +} + +void CollationsEditor::setupDefShortcuts() +{ + +} + +QToolBar* CollationsEditor::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return ui->toolbar; +} + +void CollationsEditor::init() +{ + ui->setupUi(this); + initActions(); + + setFont(CFG_UI.Fonts.SqlEditor.get()); + + model = new CollationsEditorModel(this); + collationFilterModel = new QSortFilterProxyModel(this); + collationFilterModel->setSourceModel(model); + ui->collationList->setModel(collationFilterModel); + + dbListModel = new SelectableDbModel(this); + dbListModel->setDisabledVersion(2); + dbListModel->setSourceModel(DBTREE->getModel()); + ui->databaseList->setModel(dbListModel); + ui->databaseList->expandAll(); + + model->setData(COLLATIONS->getAllCollations()); + + connect(ui->collationList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(collationSelected(QItemSelection,QItemSelection))); + connect(ui->collationList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState())); + connect(ui->codeEdit, SIGNAL(textChanged()), this, SLOT(updateModified())); + connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateModified())); + connect(ui->allDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified())); + connect(ui->selectedDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified())); + connect(ui->langCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified())); + + connect(dbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified())); + connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant))); + + // Language plugins + foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins()) + ui->langCombo->addItem(plugin->getLanguage()); + + // Syntax highlighting plugins + foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins()) + highlighterPlugins[plugin->getLanguageName()] = plugin; + + updateState(); +} + +int CollationsEditor::getCurrentCollationRow() const +{ + QModelIndexList idxList = ui->collationList->selectionModel()->selectedIndexes(); + if (idxList.size() == 0) + return -1; + + return idxList.first().row(); +} + +void CollationsEditor::collationDeselected(int row) +{ + model->setName(row, ui->nameEdit->text()); + model->setLang(row, ui->langCombo->currentText()); + model->setAllDatabases(row, ui->allDatabasesRadio->isChecked()); + model->setCode(row, ui->codeEdit->toPlainText()); + model->setModified(row, currentModified); + + if (ui->selectedDatabasesRadio->isChecked()) + model->setDatabases(row, getCurrentDatabases()); + + model->validateNames(); +} + +void CollationsEditor::collationSelected(int row) +{ + updatesForSelection = true; + ui->nameEdit->setText(model->getName(row)); + ui->codeEdit->setPlainText(model->getCode(row)); + ui->langCombo->setCurrentText(model->getLang(row)); + + // Databases + dbListModel->setDatabases(model->getDatabases(row)); + ui->databaseList->expandAll(); + + if (model->getAllDatabases(row)) + ui->allDatabasesRadio->setChecked(true); + else + ui->selectedDatabasesRadio->setChecked(true); + + updatesForSelection = false; + currentModified = false; + + updateCurrentCollationState(); +} + +void CollationsEditor::clearEdits() +{ + ui->nameEdit->setText(QString::null); + ui->codeEdit->setPlainText(QString::null); + ui->langCombo->setCurrentText(QString::null); + ui->allDatabasesRadio->setChecked(true); + ui->langCombo->setCurrentIndex(-1); +} + +void CollationsEditor::selectCollation(int row) +{ + if (!model->isValidRowIndex(row)) + return; + + ui->collationList->selectionModel()->setCurrentIndex(model->index(row), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); +} + +QStringList CollationsEditor::getCurrentDatabases() const +{ + return dbListModel->getDatabases(); +} + +void CollationsEditor::setFont(const QFont& font) +{ + ui->codeEdit->setFont(font); +} + +void CollationsEditor::help() +{ + static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_collations"); + QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode)); +} + +void CollationsEditor::commit() +{ + int row = getCurrentCollationRow(); + if (model->isValidRowIndex(row)) + collationDeselected(row); + + QList collations = model->getCollations(); + + COLLATIONS->setCollations(collations); + model->clearModified(); + currentModified = false; + + if (model->isValidRowIndex(row)) + selectCollation(row); + + updateState(); +} + +void CollationsEditor::rollback() +{ + int selectedBefore = getCurrentCollationRow(); + + model->setData(COLLATIONS->getAllCollations()); + currentModified = false; + clearEdits(); + + if (model->isValidRowIndex(selectedBefore)) + selectCollation(selectedBefore); + + updateState(); +} + +void CollationsEditor::newCollation() +{ + if (ui->langCombo->currentIndex() == -1 && ui->langCombo->count() > 0) + ui->langCombo->setCurrentIndex(0); + + CollationManager::CollationPtr coll = CollationManager::CollationPtr::create(); + coll->name = generateUniqueName("collation", model->getCollationNames()); + + if (ui->langCombo->currentIndex() > -1) + coll->lang = ui->langCombo->currentText(); + + model->addCollation(coll); + + selectCollation(model->rowCount() - 1); +} + +void CollationsEditor::deleteCollation() +{ + int row = getCurrentCollationRow(); + model->deleteCollation(row); + clearEdits(); + + row = getCurrentCollationRow(); + if (model->isValidRowIndex(row)) + collationSelected(row); + + updateState(); +} + +void CollationsEditor::updateState() +{ + bool modified = model->isModified() || currentModified; + bool valid = model->isValid(); + + actionMap[COMMIT]->setEnabled(modified && valid); + actionMap[ROLLBACK]->setEnabled(modified); + actionMap[DELETE]->setEnabled(ui->collationList->selectionModel()->selectedIndexes().size() > 0); +} + +void CollationsEditor::updateCurrentCollationState() +{ + int row = getCurrentCollationRow(); + bool validRow = model->isValidRowIndex(row); + ui->rightWidget->setEnabled(validRow); + if (!validRow) + { + setValidState(ui->langCombo, true); + setValidState(ui->nameEdit, true); + setValidState(ui->codeEdit, true); + return; + } + + QString name = ui->nameEdit->text(); + bool nameOk = model->isAllowedName(row, name) && !name.trimmed().isEmpty(); + setValidState(ui->nameEdit, nameOk, tr("Enter a non-empty, unique name of the collation.")); + + bool langOk = ui->langCombo->currentIndex() >= 0; + ui->codeGroup->setEnabled(langOk); + ui->databasesGroup->setEnabled(langOk); + ui->nameEdit->setEnabled(langOk); + ui->nameLabel->setEnabled(langOk); + ui->databaseList->setEnabled(ui->selectedDatabasesRadio->isChecked()); + setValidState(ui->langCombo, langOk, tr("Pick the implementation language.")); + + bool codeOk = !ui->codeEdit->toPlainText().trimmed().isEmpty(); + setValidState(ui->codeEdit, codeOk, tr("Enter a non-empty implementation code.")); + + // Syntax highlighter + QString lang = ui->langCombo->currentText(); + if (lang != currentHighlighterLang) + { + QSyntaxHighlighter* highlighter = nullptr; + if (currentHighlighter) + { + // A pointers swap with local var - this is necessary, cause deleting highlighter + // triggers textChanged on QPlainTextEdit, which then calls this method, + // so it becomes an infinite recursion with deleting the same pointer. + // We set the pointer to null first, then delete it. That way it's safe. + highlighter = currentHighlighter; + currentHighlighter = nullptr; + delete highlighter; + } + + if (langOk && highlighterPlugins.contains(lang)) + { + currentHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->codeEdit); + } + + currentHighlighterLang = lang; + } + model->setValid(row, langOk && codeOk && nameOk); + updateState(); +} + +void CollationsEditor::collationSelected(const QItemSelection& selected, const QItemSelection& deselected) +{ + int deselCnt = deselected.indexes().size(); + int selCnt = selected.indexes().size(); + + if (deselCnt > 0) + collationDeselected(deselected.indexes().first().row()); + + if (selCnt > 0) + collationSelected(selected.indexes().first().row()); + + if (deselCnt > 0 && selCnt == 0) + { + currentModified = false; + clearEdits(); + } +} + +void CollationsEditor::updateModified() +{ + if (updatesForSelection) + return; + + int row = getCurrentCollationRow(); + if (model->isValidRowIndex(row)) + { + bool nameDiff = model->getName(row) != ui->nameEdit->text(); + bool codeDiff = model->getCode(row) != ui->codeEdit->toPlainText(); + bool langDiff = model->getLang(row) != ui->langCombo->currentText(); + bool allDatabasesDiff = model->getAllDatabases(row) != ui->allDatabasesRadio->isChecked(); + bool dbDiff = getCurrentDatabases().toSet() != model->getDatabases(row).toSet(); // QSet to ignore order + + currentModified = (nameDiff || codeDiff || langDiff || allDatabasesDiff || dbDiff); + } + + updateCurrentCollationState(); +} + +void CollationsEditor::applyFilter(const QString& value) +{ + // + // See FunctionsEditor::applyFilter() for details why we remember current selection and restore it at the end. + // + + int row = getCurrentCollationRow(); + ui->collationList->selectionModel()->clearSelection(); + + collationFilterModel->setFilterFixedString(value); + + selectCollation(row); +} + +void CollationsEditor::changeFont(const QVariant& font) +{ + setFont(font.value()); +} + + +bool CollationsEditor::isUncommited() const +{ + return model->isModified(); +} + +QString CollationsEditor::getQuitUncommitedConfirmMessage() const +{ + return tr("Collations editor window has uncommited modifications."); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h new file mode 100644 index 0000000..62cb281 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h @@ -0,0 +1,89 @@ +#ifndef COLLATIONSEDITOR_H +#define COLLATIONSEDITOR_H + +#include "mdichild.h" +#include "common/extactioncontainer.h" +#include +#include +#include + +namespace Ui { + class CollationsEditor; +} + +class SyntaxHighlighterPlugin; +class SelectableDbModel; +class CollationsEditorModel; +class QSortFilterProxyModel; +class QSyntaxHighlighter; + +class GUI_API_EXPORT CollationsEditor : public MdiChild +{ + Q_OBJECT + + public: + enum Action + { + COMMIT, + ROLLBACK, + ADD, + DELETE, + HELP + }; + + enum ToolBar + { + TOOLBAR + }; + + explicit CollationsEditor(QWidget *parent = 0); + ~CollationsEditor(); + + bool restoreSessionNextTime(); + bool isUncommited() const; + QString getQuitUncommitedConfirmMessage() const; + + protected: + QVariant saveSession(); + bool restoreSession(const QVariant &sessionValue); + Icon* getIconNameForMdiWindow(); + QString getTitleForMdiWindow(); + void createActions(); + void setupDefShortcuts(); + QToolBar* getToolBar(int toolbar) const; + + private: + void init(); + int getCurrentCollationRow() const; + void collationDeselected(int row); + void collationSelected(int row); + void clearEdits(); + void selectCollation(int row); + QStringList getCurrentDatabases() const; + void setFont(const QFont& font); + + Ui::CollationsEditor *ui = nullptr; + CollationsEditorModel* model = nullptr; + QSortFilterProxyModel* collationFilterModel = nullptr; + SelectableDbModel* dbListModel = nullptr; + QHash highlighterPlugins; + QSyntaxHighlighter* currentHighlighter = nullptr; + QString currentHighlighterLang; + bool currentModified = false; + bool updatesForSelection = false; + + private slots: + void help(); + void commit(); + void rollback(); + void newCollation(); + void deleteCollation(); + void updateState(); + void updateCurrentCollationState(); + void collationSelected(const QItemSelection& selected, const QItemSelection& deselected); + void updateModified(); + void applyFilter(const QString& value); + void changeFont(const QVariant& font); +}; + +#endif // COLLATIONSEDITOR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui new file mode 100644 index 0000000..635ae59 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui @@ -0,0 +1,210 @@ + + + CollationsEditor + + + + 0 + 0 + 765 + 529 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + 1 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter collations + + + + + + + + + + + + 4 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Collation name: + + + + + + + + + + Implementation language: + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + 1 + + + + Databases + + + + + + Register in all databases + + + + + + + Register in following databases: + + + + + + + false + + + + + + + + + 0 + 2 + + + + Implementation code: + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp new file mode 100644 index 0000000..05ca4e1 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp @@ -0,0 +1,287 @@ +#include "collationseditormodel.h" +#include "common/unused.h" +#include "common/strhash.h" +#include "services/pluginmanager.h" +#include "plugins/scriptingplugin.h" +#include "icon.h" + +#define SETTER(X, Y) \ + if (!isValidRowIndex(row) || X == Y) \ + return; \ + \ + X = Y; \ + emitDataChanged(row); + +#define GETTER(X, Y) \ + if (!isValidRowIndex(row)) \ + return Y; \ + \ + return X; + +CollationsEditorModel::CollationsEditorModel(QObject *parent) : + QAbstractListModel(parent) +{ + init(); +} + +void CollationsEditorModel::clearModified() +{ + beginResetModel(); + foreach (Collation* coll, collationList) + coll->modified = false; + + listModified = false; + originalCollationList = collationList; + + endResetModel(); +} + +bool CollationsEditorModel::isModified() const +{ + if (collationList != originalCollationList) + return true; + + foreach (Collation* coll, collationList) + { + if (coll->modified) + return true; + } + return false; +} + +bool CollationsEditorModel::isModified(int row) const +{ + GETTER(collationList[row]->modified, false); +} + +void CollationsEditorModel::setModified(int row, bool modified) +{ + SETTER(collationList[row]->modified, modified); +} + +void CollationsEditorModel::setName(int row, const QString& name) +{ + SETTER(collationList[row]->data->name, name); +} + +QString CollationsEditorModel::getName(int row) const +{ + GETTER(collationList[row]->data->name, QString()); +} + +void CollationsEditorModel::setLang(int row, const QString& lang) +{ + SETTER(collationList[row]->data->lang, lang); +} + +QString CollationsEditorModel::getLang(int row) const +{ + GETTER(collationList[row]->data->lang, QString()); +} + +void CollationsEditorModel::setAllDatabases(int row, bool allDatabases) +{ + SETTER(collationList[row]->data->allDatabases, allDatabases); +} + +bool CollationsEditorModel::getAllDatabases(int row) const +{ + GETTER(collationList[row]->data->allDatabases, true); +} + +void CollationsEditorModel::setCode(int row, const QString& code) +{ + SETTER(collationList[row]->data->code, code); +} + +QString CollationsEditorModel::getCode(int row) const +{ + GETTER(collationList[row]->data->code, QString()); +} + +void CollationsEditorModel::setDatabases(int row, const QStringList& databases) +{ + SETTER(collationList[row]->data->databases, databases); +} + +QStringList CollationsEditorModel::getDatabases(int row) +{ + GETTER(collationList[row]->data->databases, QStringList()); +} + +bool CollationsEditorModel::isValid(int row) const +{ + GETTER(collationList[row]->valid, false); +} + +void CollationsEditorModel::setValid(int row, bool valid) +{ + SETTER(collationList[row]->valid, valid); +} + +bool CollationsEditorModel::isValid() const +{ + foreach (Collation* coll, collationList) + { + if (!coll->valid) + return false; + } + return true; +} + +void CollationsEditorModel::setData(const QList& collations) +{ + beginResetModel(); + + Collation* collationPtr = nullptr; + foreach (collationPtr, collationList) + delete collationPtr; + + collationList.clear(); + + foreach (const CollationManager::CollationPtr& coll, collations) + collationList << new Collation(coll); + + listModified = false; + originalCollationList = collationList; + + endResetModel(); +} + +void CollationsEditorModel::addCollation(const CollationManager::CollationPtr& collation) +{ + int row = collationList.size(); + + beginInsertRows(QModelIndex(), row, row); + + collationList << new Collation(collation); + listModified = true; + + endInsertRows(); +} + +void CollationsEditorModel::deleteCollation(int row) +{ + if (!isValidRowIndex(row)) + return; + + beginRemoveRows(QModelIndex(), row, row); + + delete collationList[row]; + collationList.removeAt(row); + + listModified = true; + + endRemoveRows(); +} + +QList CollationsEditorModel::getCollations() const +{ + QList results; + + foreach (Collation* coll, collationList) + results << coll->data; + + return results; +} + +QStringList CollationsEditorModel::getCollationNames() const +{ + QStringList names; + foreach (Collation* coll, collationList) + names << coll->data->name; + + return names; +} + +void CollationsEditorModel::validateNames() +{ + StrHash> counter; + + int row = 0; + foreach (Collation* coll, collationList) + { + coll->valid &= true; + counter[coll->data->name] << row++; + } + + QHashIterator> cntIt = counter.iterator(); + while (cntIt.hasNext()) + { + cntIt.next(); + if (cntIt.value().size() > 1) + { + foreach (int cntRow, cntIt.value()) + setValid(cntRow, false); + } + } + + QModelIndex idx; + for (int i = 0; i < collationList.size(); i++) + { + idx = index(i); + emit dataChanged(idx, idx); + } +} + +bool CollationsEditorModel::isAllowedName(int rowToSkip, const QString& nameToValidate) +{ + QStringList names = getCollationNames(); + names.removeAt(rowToSkip); + return !names.contains(nameToValidate, Qt::CaseInsensitive); +} + +bool CollationsEditorModel::isValidRowIndex(int row) const +{ + return (row >= 0 && row < collationList.size()); +} + +int CollationsEditorModel::rowCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return collationList.size(); +} + +QVariant CollationsEditorModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || !isValidRowIndex(index.row())) + return QVariant(); + + if (role == Qt::DisplayRole) + return collationList[index.row()]->data->name; + + if (role == Qt::DecorationRole && langToIcon.contains(collationList[index.row()]->data->lang)) + { + QIcon icon = langToIcon[collationList[index.row()]->data->lang]; + if (!collationList[index.row()]->valid) + icon = Icon::merge(icon, Icon::ERROR); + + return icon; + } + + return QVariant(); + +} + +void CollationsEditorModel::init() +{ + foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins()) + langToIcon[plugin->getLanguage()] = QIcon(plugin->getIconPath()); +} + +void CollationsEditorModel::emitDataChanged(int row) +{ + QModelIndex idx = index(row); + emit dataChanged(idx, idx); +} + +CollationsEditorModel::Collation::Collation() +{ + data = CollationManager::CollationPtr::create(); +} + +CollationsEditorModel::Collation::Collation(const CollationManager::CollationPtr& other) +{ + data = CollationManager::CollationPtr::create(*other); + originalName = data->name; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h new file mode 100644 index 0000000..0c17c5b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h @@ -0,0 +1,77 @@ +#ifndef COLLATIONSEDITORMODEL_H +#define COLLATIONSEDITORMODEL_H + +#include "services/collationmanager.h" +#include "guiSQLiteStudio_global.h" +#include +#include +#include + +class GUI_API_EXPORT CollationsEditorModel : public QAbstractListModel +{ + Q_OBJECT + public: + using QAbstractItemModel::setData; + + explicit CollationsEditorModel(QObject *parent = 0); + + void clearModified(); + bool isModified() const; + bool isModified(int row) const; + void setModified(int row, bool modified); + void setName(int row, const QString& name); + QString getName(int row) const; + void setLang(int row, const QString& lang); + QString getLang(int row) const; + void setAllDatabases(int row, bool allDatabases); + bool getAllDatabases(int row) const; + void setCode(int row, const QString& code); + QString getCode(int row) const; + void setDatabases(int row, const QStringList& databases); + QStringList getDatabases(int row); + bool isValid(int row) const; + void setValid(int row, bool valid); + bool isValid() const; + void setData(const QList& collations); + void addCollation(const CollationManager::CollationPtr& collation); + void deleteCollation(int row); + QList getCollations() const; + QStringList getCollationNames() const; + void validateNames(); + bool isAllowedName(int rowToSkip, const QString& nameToValidate); + bool isValidRowIndex(int row) const; + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role) const; + + private: + struct Collation + { + Collation(); + Collation(const CollationManager::CollationPtr& other); + + CollationManager::CollationPtr data; + bool modified = false; + bool valid = true; + QString originalName; + }; + + void init(); + void emitDataChanged(int row); + + QList collationList; + + /** + * @brief List of collation pointers before modifications. + * + * This list is kept to check for modifications in the overall list of collations. + * Pointers on this list may be already deleted, so don't use them! + * It's only used to compare list of pointers to collationList, so it can tell you + * if the list was modified in regards of adding or deleting collations. + */ + QList originalCollationList; + QHash langToIcon; + bool listModified = false; +}; + +#endif // COLLATIONSEDITORMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp new file mode 100644 index 0000000..2d8897b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp @@ -0,0 +1,395 @@ +#include "constrainttabmodel.h" +#include "common/unused.h" +#include "iconmanager.h" +#include "common/utils_sql.h" +#include + +ConstraintTabModel::ConstraintTabModel(QObject *parent) : + QAbstractTableModel(parent) +{ +} + +int ConstraintTabModel::rowCount(const QModelIndex& parent) const +{ + UNUSED(parent); + if (createTable.isNull()) + return 0; + + int cnt = 0; + foreach (SqliteCreateTable::Column* col, createTable->columns) + cnt += col->constraints.size(); + + cnt += createTable->constraints.size(); + return cnt; +} + +int ConstraintTabModel::columnCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return 4; +} + +QVariant ConstraintTabModel::data(const QModelIndex& index, int role) const +{ + if (createTable.isNull()) + return QVariant(); + + int constrIdx = index.row(); + int currIdx = -1; + foreach (SqliteCreateTable::Column* column, createTable->columns) + { + foreach (SqliteCreateTable::Column::Constraint* constr, column->constraints) + { + currIdx++; + + if (currIdx == constrIdx) + return data(constr, index.column(), role); + } + } + + foreach (SqliteCreateTable::Constraint* constr, createTable->constraints) + { + currIdx++; + + if (currIdx == constrIdx) + return data(constr, index.column(), role); + } + + return QVariant(); +} + +QVariant ConstraintTabModel::data(SqliteCreateTable::Constraint* constr, int column, int role) const +{ + switch (getColumn(column)) + { + case ConstraintTabModel::Columns::SCOPE: + { + if (role == Qt::DisplayRole) + return tr("Table", "table constraints"); + + break; + } + case Columns::TYPE: + { + if (role == Qt::DisplayRole) + return getTypeLabel(constr->type); + + if (role == Qt::DecorationRole) + return getTypeIcon(constr->type); + + break; + } + case Columns::NAME: + { + if (role == Qt::DisplayRole) + return stripObjName(constr->name, createTable->dialect); + + break; + } + case Columns::DETAILS: + { + if (role == Qt::DisplayRole) + return getDetails(constr); + + break; + } + } + return QVariant(); +} + +QVariant ConstraintTabModel::data(SqliteCreateTable::Column::Constraint* constr, int column, int role) const +{ + switch (getColumn(column)) + { + case ConstraintTabModel::Columns::SCOPE: + { + if (role == Qt::DisplayRole) + { + QString colName = dynamic_cast(constr->parentStatement())->name; + return tr("Column (%1)", "table constraints").arg(colName); + } + + break; + } + case Columns::TYPE: + { + if (role == Qt::DisplayRole) + return getTypeLabel(constr->type); + + if (role == Qt::DecorationRole) + return getTypeIcon(constr->type); + + break; + } + case Columns::NAME: + { + if (role == Qt::DisplayRole) + return stripObjName(constr->name, createTable->dialect); + + break; + } + case Columns::DETAILS: + { + if (role == Qt::DisplayRole) + return getDetails(constr); + + break; + } + } + return QVariant(); +} + +QVariant ConstraintTabModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QAbstractTableModel::headerData(section, orientation, role); + + if (orientation == Qt::Vertical) + return section + 1; + + switch (getColumn(section)) + { + case ConstraintTabModel::Columns::SCOPE: + return tr("Scope", "table constraints"); + case Columns::TYPE: + return tr("Type", "table constraints"); + case Columns::DETAILS: + return tr("Details", "table constraints"); + case Columns::NAME: + return tr("Name", "table constraints"); + } + return QVariant(); +} + +void ConstraintTabModel::setCreateTable(const QPointer& value) +{ + beginResetModel(); + createTable = value; + endResetModel(); +} + +ConstraintTabModel::Columns ConstraintTabModel::getColumn(int idx) const +{ + return static_cast(idx); +} + +QString ConstraintTabModel::getTypeLabel(SqliteCreateTable::Constraint::Type type) const +{ + switch (type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return "PRIMARY KEY"; + case SqliteCreateTable::Constraint::UNIQUE: + return "UNIQUE"; + case SqliteCreateTable::Constraint::CHECK: + return "CHECK"; + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return "FOREIGN KEY"; + case SqliteCreateTable::Constraint::NAME_ONLY: + return QString::null; + } + return QString::null; +} + +QString ConstraintTabModel::getTypeLabel(SqliteCreateTable::Column::Constraint::Type type) const +{ + switch (type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return "PRIMARY KEY"; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return "NOT NULL"; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return "UNIQUE"; + case SqliteCreateTable::Column::Constraint::CHECK: + return "CHECK"; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return "DEFAULT"; + case SqliteCreateTable::Column::Constraint::COLLATE: + return "COLLATE"; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return "FOREIGN KEY"; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return QString::null; +} + +QIcon ConstraintTabModel::getTypeIcon(SqliteCreateTable::Constraint::Type type) const +{ + switch (type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return ICONS.CONSTRAINT_PRIMARY_KEY; + case SqliteCreateTable::Constraint::UNIQUE: + return ICONS.CONSTRAINT_UNIQUE; + case SqliteCreateTable::Constraint::CHECK: + return ICONS.CONSTRAINT_CHECK; + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return ICONS.CONSTRAINT_FOREIGN_KEY; + case SqliteCreateTable::Constraint::NAME_ONLY: + return QIcon(); + } + return QIcon(); +} + +QIcon ConstraintTabModel::getTypeIcon(SqliteCreateTable::Column::Constraint::Type type) const +{ + switch (type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return ICONS.CONSTRAINT_PRIMARY_KEY; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return ICONS.CONSTRAINT_NOT_NULL; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return ICONS.CONSTRAINT_UNIQUE; + case SqliteCreateTable::Column::Constraint::CHECK: + return ICONS.CONSTRAINT_CHECK; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return ICONS.CONSTRAINT_DEFAULT; + case SqliteCreateTable::Column::Constraint::COLLATE: + return ICONS.CONSTRAINT_COLLATION; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return ICONS.CONSTRAINT_FOREIGN_KEY; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return QIcon(); +} + +QString ConstraintTabModel::getDetails(SqliteCreateTable::Constraint* constr) const +{ + switch (constr->type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return getPkDetails(constr); + case SqliteCreateTable::Constraint::UNIQUE: + return getUniqueDetails(constr); + case SqliteCreateTable::Constraint::CHECK: + return getCheckDetails(constr); + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return getFkDetails(constr); + case SqliteCreateTable::Constraint::NAME_ONLY: + return QString::null; + } + return QString::null; +} + +QString ConstraintTabModel::getDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + switch (constr->type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return getPkDetails(constr); + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return getNotNullDetails(constr); + case SqliteCreateTable::Column::Constraint::UNIQUE: + return getUniqueDetails(constr); + case SqliteCreateTable::Column::Constraint::CHECK: + return getCheckDetails(constr); + case SqliteCreateTable::Column::Constraint::DEFAULT: + return getDefaultDetails(constr); + case SqliteCreateTable::Column::Constraint::COLLATE: + return getCollateDetails(constr); + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return getFkDetails(constr); + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return QString::null; +} + +QString ConstraintTabModel::getPkDetails(SqliteCreateTable::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getUniqueDetails(SqliteCreateTable::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getCheckDetails(SqliteCreateTable::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getFkDetails(SqliteCreateTable::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getPkDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getFkDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "REFERENCES", Qt::CaseInsensitive); + return getConstrDetails(constr, idx); +} + +QString ConstraintTabModel::getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "NULL", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "COLLATE", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "DEFAULT", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString ConstraintTabModel::getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const +{ + return getConstrDetails(constr->tokens, tokenOffset); +} + +QString ConstraintTabModel::getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const +{ + return getConstrDetails(constr->tokens, tokenOffset); +} + +QString ConstraintTabModel::getConstrDetails(const TokenList& constrTokens, int tokenOffset) const +{ + TokenList tokens = constrTokens.mid(tokenOffset); + tokens.trimLeft(); + return tokens.detokenize(); +} + +void ConstraintTabModel::updateModel() +{ + beginResetModel(); + endResetModel(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h new file mode 100644 index 0000000..f93415b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h @@ -0,0 +1,70 @@ +#ifndef CONSTRAINTTABMODEL_H +#define CONSTRAINTTABMODEL_H + +#include "parser/ast/sqlitecreatetable.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class GUI_API_EXPORT ConstraintTabModel : public QAbstractTableModel +{ + Q_OBJECT + public: + explicit ConstraintTabModel(QObject *parent = 0); + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role) const; + QVariant data(SqliteCreateTable::Constraint* constr, int column, int role) const; + QVariant data(SqliteCreateTable::Column::Constraint* constr, int column, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + void setCreateTable(const QPointer& value); + + private: + enum class Columns + { + SCOPE, + TYPE, + NAME, + DETAILS + }; + + Columns getColumn(int idx) const; + + QString getTypeLabel(SqliteCreateTable::Constraint::Type type) const; + QString getTypeLabel(SqliteCreateTable::Column::Constraint::Type type) const; + + QIcon getTypeIcon(SqliteCreateTable::Constraint::Type type) const; + QIcon getTypeIcon(SqliteCreateTable::Column::Constraint::Type type) const; + + QString getDetails(SqliteCreateTable::Constraint* constr) const; + QString getDetails(SqliteCreateTable::Column::Constraint* constr) const; + + QString getPkDetails(SqliteCreateTable::Constraint* constr) const; + QString getUniqueDetails(SqliteCreateTable::Constraint* constr) const; + QString getCheckDetails(SqliteCreateTable::Constraint* constr) const; + QString getFkDetails(SqliteCreateTable::Constraint* constr) const; + + QString getPkDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getFkDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const; + QString getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const; + + QString getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const; + QString getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const; + QString getConstrDetails(const TokenList& constrTokens, int tokenOffset) const; + + QPointer createTable; + + signals: + + public slots: + void updateModel(); + +}; + +#endif // CONSTRAINTTABMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp new file mode 100644 index 0000000..3aeccfb --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp @@ -0,0 +1,150 @@ +#include "ddlhistorywindow.h" +#include "ui_ddlhistorywindow.h" +#include "services/config.h" +#include "common/userinputfilter.h" +#include "common/extlineedit.h" +#include "dblistmodel.h" +#include "ddlhistorymodel.h" +#include "common/unused.h" +#include "iconmanager.h" +#include +#include +#include + +DdlHistoryWindow::DdlHistoryWindow(QWidget *parent) : + MdiChild(parent), + ui(new Ui::DdlHistoryWindow) +{ + init(); +} + +DdlHistoryWindow::~DdlHistoryWindow() +{ + delete ui; +} + +void DdlHistoryWindow::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void DdlHistoryWindow::init() +{ + ui->setupUi(this); + + dataModel = CFG->getDdlHistoryModel(); + + dbListModel = new QStringListModel(this); + QStringList dbList = dataModel->getDbNames(); + dbList.prepend(""); + dbListModel->setStringList(dbList); + ui->comboBox->setModel(dbListModel); + ui->comboBox->setCurrentIndex(-1); + connect(ui->comboBox, SIGNAL(currentTextChanged(QString)), this, SLOT(applyFilter(QString))); + connect(dataModel, SIGNAL(refreshed()), this, SLOT(refreshDbList())); + + ui->tableView->setModel(dataModel); + ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + ui->tableView->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + + connect(ui->tableView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + this, SLOT(activated(QModelIndex,QModelIndex))); +} + +void DdlHistoryWindow::activated(const QModelIndex& current, const QModelIndex& previous) +{ + UNUSED(previous); + + int row = current.row(); + QString dbName = dataModel->data(dataModel->index(row, 0)).toString(); + QString dbFile = dataModel->data(dataModel->index(row, 1)).toString(); + QString dateString = dataModel->data(dataModel->index(row, 2)).toString(); + QDate date = QDate::fromString(dateString, "yyyy-MM-dd"); + + static const QString templ = tr("-- Queries executed on database %1 (%2)\n" + "-- Date and time of execution: %3\n" + "%4"); + + QStringList contentEntries; + QList entries = CFG->getDdlHistoryFor(dbName, dbFile, date); + foreach (Config::DdlHistoryEntryPtr entry, entries) + { + contentEntries << templ.arg(entry->dbName).arg(entry->dbFile) + .arg(entry->timestamp.toString("yyyy-MM-dd HH:mm:ss")) + .arg(entry->queries); + } + + ui->ddlEdit->setPlainText(contentEntries.join("\n\n")); +} + +void DdlHistoryWindow::applyFilter(const QString& filterValue) +{ + dataModel->setDbNameForFilter(filterValue); +} + +void DdlHistoryWindow::refreshDbList() +{ + QStringList dbList = dataModel->getDbNames(); + dbList.prepend(""); + dbListModel->setStringList(dbList); +} + +bool DdlHistoryWindow::restoreSessionNextTime() +{ + return false; +} + +QVariant DdlHistoryWindow::saveSession() +{ + return QVariant(); +} + +bool DdlHistoryWindow::restoreSession(const QVariant& sessionValue) +{ + UNUSED(sessionValue); + return true; +} + +Icon* DdlHistoryWindow::getIconNameForMdiWindow() +{ + return ICONS.DDL_HISTORY; +} + +QString DdlHistoryWindow::getTitleForMdiWindow() +{ + return tr("DDL history"); +} + +void DdlHistoryWindow::createActions() +{ +} + +void DdlHistoryWindow::setupDefShortcuts() +{ +} + +QToolBar* DdlHistoryWindow::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return nullptr; +} + + +bool DdlHistoryWindow::isUncommited() const +{ + return false; +} + +QString DdlHistoryWindow::getQuitUncommitedConfirmMessage() const +{ + return QString(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h new file mode 100644 index 0000000..16a07ce --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h @@ -0,0 +1,54 @@ +#ifndef DDLHISTORYWINDOW_H +#define DDLHISTORYWINDOW_H + +#include "mdichild.h" + +namespace Ui { + class DdlHistoryWindow; +} + +class QStringListModel; +class UserInputFilter; +class DdlHistoryModel; + +class GUI_API_EXPORT DdlHistoryWindow : public MdiChild +{ + Q_OBJECT + + public: + enum ToolBar + { + }; + + explicit DdlHistoryWindow(QWidget *parent = 0); + ~DdlHistoryWindow(); + + bool restoreSessionNextTime(); + bool isUncommited() const; + QString getQuitUncommitedConfirmMessage() const; + + protected: + void changeEvent(QEvent *e); + QVariant saveSession(); + bool restoreSession(const QVariant& sessionValue); + Icon* getIconNameForMdiWindow(); + QString getTitleForMdiWindow(); + void createActions(); + void setupDefShortcuts(); + QToolBar* getToolBar(int toolbar) const; + + private: + void init(); + + Ui::DdlHistoryWindow *ui = nullptr; + QStringListModel* dbListModel = nullptr; + DdlHistoryModel* dataModel = nullptr; + UserInputFilter* filter = nullptr; + + private slots: + void activated(const QModelIndex& current, const QModelIndex& previous); + void applyFilter(const QString& filterValue); + void refreshDbList(); +}; + +#endif // DDLHISTORYWINDOW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui new file mode 100644 index 0000000..6591198 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui @@ -0,0 +1,126 @@ + + + DdlHistoryWindow + + + + 0 + 0 + 749 + 599 + + + + Form + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter by database: + + + + + + + + 200 + 0 + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + + 0 + 1 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + + + + 0 + 2 + + + + true + + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    +
    + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp new file mode 100644 index 0000000..7856a5e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp @@ -0,0 +1,652 @@ +#include "editorwindow.h" +#include "ui_editorwindow.h" +#include "uiutils.h" +#include "datagrid/sqlquerymodel.h" +#include "iconmanager.h" +#include "dblistmodel.h" +#include "services/notifymanager.h" +#include "dbtree/dbtree.h" +#include "datagrid/sqlqueryitem.h" +#include "datagrid/sqlqueryview.h" +#include "mainwindow.h" +#include "mdiarea.h" +#include "common/unused.h" +#include "common/extaction.h" +#include "uiconfig.h" +#include "services/config.h" +#include "parser/lexer.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include +#include +#include +#include +#include +#include +#include + +CFG_KEYS_DEFINE(EditorWindow) +EditorWindow::ResultsDisplayMode EditorWindow::resultsDisplayMode; +QHash EditorWindow::staticActions; +QHash EditorWindow::staticActionGroups; + +EditorWindow::EditorWindow(QWidget *parent) : + MdiChild(parent), + ui(new Ui::EditorWindow) +{ + ui->setupUi(this); + init(); +} + +EditorWindow::EditorWindow(const EditorWindow& editor) : + MdiChild(editor.parentWidget()), + ui(new Ui::EditorWindow) +{ + ui->setupUi(this); + init(); + ui->sqlEdit->setAutoCompletion(false); + ui->sqlEdit->setPlainText(editor.ui->sqlEdit->toPlainText()); + ui->sqlEdit->setAutoCompletion(true); +} + +EditorWindow::~EditorWindow() +{ + delete ui; +} + +void EditorWindow::staticInit() +{ + qRegisterMetaType("EditorWindow"); + resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY; + loadTabsMode(); + createStaticActions(); +} + +void EditorWindow::insertAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertAction(action, toolbar); +} + +void EditorWindow::insertActionBefore(ExtActionPrototype* action, EditorWindow::Action beforeAction, EditorWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); +} + +void EditorWindow::insertActionAfter(ExtActionPrototype* action, EditorWindow::Action afterAction, EditorWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); +} + +void EditorWindow::removeAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar) +{ + ExtActionContainer::removeAction(action, toolbar); +} + +void EditorWindow::init() +{ + setFocusProxy(ui->sqlEdit); + updateResultsDisplayMode(); + + resultsModel = new SqlQueryModel(this); + ui->dataView->init(resultsModel); + + createDbCombo(); + initActions(); + updateShortcutTips(); + + Db* currentDb = getCurrentDb(); + resultsModel->setDb(currentDb); + ui->sqlEdit->setDb(currentDb); + + connect(resultsModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful())); + connect(resultsModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString))); + connect(resultsModel, SIGNAL(totalRowsAndPagesAvailable()), this, SLOT(totalRowsAndPagesAvailable())); + + // SQL history list + ui->historyList->setModel(CFG->getSqlHistoryModel()); + ui->historyList->resizeColumnToContents(1); + connect(ui->historyList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + this, SLOT(historyEntrySelected(QModelIndex,QModelIndex))); + connect(ui->historyList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(historyEntryActivated(QModelIndex))); + + updateState(); +} + +void EditorWindow::loadTabsMode() +{ + QString tabsString = CFG_UI.General.SqlEditorTabs.get(); + if (tabsString == "SEPARATE_TAB") + resultsDisplayMode = ResultsDisplayMode::SEPARATE_TAB; + else if (tabsString == "BELOW_QUERY") + resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY; +} + +void EditorWindow::createStaticActions() +{ + staticActions[RESULTS_IN_TAB] = new ExtAction(ICONS.RESULTS_IN_TAB, tr("Results in the separate tab"), MainWindow::getInstance()); + staticActions[RESULTS_BELOW] = new ExtAction(ICONS.RESULTS_BELOW, tr("Results below the query"), MainWindow::getInstance()); + + staticActionGroups[ActionGroup::RESULTS_POSITIONING] = new QActionGroup(MainWindow::getInstance()); + staticActionGroups[ActionGroup::RESULTS_POSITIONING]->addAction(staticActions[RESULTS_IN_TAB]); + staticActionGroups[ActionGroup::RESULTS_POSITIONING]->addAction(staticActions[RESULTS_BELOW]); + + connect(staticActions[RESULTS_BELOW], &QAction::triggered, [=]() + { + resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY; + CFG_UI.General.SqlEditorTabs.set("BELOW_QUERY"); + }); + connect(staticActions[RESULTS_IN_TAB], &QAction::triggered, [=]() + { + resultsDisplayMode = ResultsDisplayMode::SEPARATE_TAB; + CFG_UI.General.SqlEditorTabs.set("SEPARATE_TAB"); + }); + + staticActions[RESULTS_BELOW]->setCheckable(true); + staticActions[RESULTS_IN_TAB]->setCheckable(true); + if (resultsDisplayMode == ResultsDisplayMode::BELOW_QUERY) + staticActions[RESULTS_BELOW]->setChecked(true); + else + staticActions[RESULTS_IN_TAB]->setChecked(true); +} + +Icon* EditorWindow::getIconNameForMdiWindow() +{ + return ICONS.OPEN_SQL_EDITOR; +} + +QString EditorWindow::getTitleForMdiWindow() +{ + QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles(); + QString title = tr("SQL editor %1").arg(sqlEditorNum++); + while (existingNames.contains(title)) + title = tr("SQL editor %1").arg(sqlEditorNum++); + + return title; +} + +QSize EditorWindow::sizeHint() const +{ + return QSize(500, 400); +} + +QAction* EditorWindow::getAction(EditorWindow::Action action) +{ + switch (action) + { + case RESULTS_BELOW: + case RESULTS_IN_TAB: + { + if (!staticActions.contains(action)) + return nullptr; + + return staticActions.value(action); + } + default: + break; + } + + return ExtActionContainer::getAction(action); +} + +QString EditorWindow::getQueryToExecute(bool doSelectCurrentQuery) +{ + QString sql; + if (ui->sqlEdit->textCursor().hasSelection()) + { + sql = ui->sqlEdit->textCursor().selectedText(); + fixTextCursorSelectedText(sql); + } + else if (CFG_UI.General.ExecuteCurrentQueryOnly.get()) + { + ui->sqlEdit->saveSelection(); + selectCurrentQuery(true); + sql = ui->sqlEdit->textCursor().selectedText(); + fixTextCursorSelectedText(sql); + if (!doSelectCurrentQuery) + ui->sqlEdit->restoreSelection(); + } + else + { + sql = ui->sqlEdit->toPlainText(); + } + return sql; +} + +bool EditorWindow::setCurrentDb(Db *db) +{ + if (dbCombo->findText(db->getName()) == -1) + return false; + + dbCombo->setCurrentText(db->getName()); + return true; +} + +void EditorWindow::setContents(const QString &sql) +{ + ui->sqlEdit->setPlainText(sql); +} + +QString EditorWindow::getContents() const +{ + return ui->sqlEdit->toPlainText(); +} + +void EditorWindow::execute() +{ + execQuery(); +} + +QToolBar* EditorWindow::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return ui->toolBar; +} + +SqlEditor* EditorWindow::getEditor() const +{ + return ui->sqlEdit; +} + +QVariant EditorWindow::saveSession() +{ + QHash sessionValue; + sessionValue["query"] = ui->sqlEdit->toPlainText(); + sessionValue["curPos"] = ui->sqlEdit->textCursor().position(); + + Db* db = getCurrentDb(); + if (db) + sessionValue["db"] = db->getName(); + + return sessionValue; +} + +bool EditorWindow::restoreSession(const QVariant& sessionValue) +{ + QHash value = sessionValue.toHash(); + if (value.size() == 0) + return true; + + if (value.contains("query")) + { + ui->sqlEdit->setAutoCompletion(false); + ui->sqlEdit->setPlainText(value["query"].toString()); + ui->sqlEdit->setAutoCompletion(true); + } + + if (value.contains("curPos")) + { + QTextCursor cursor = ui->sqlEdit->textCursor(); + cursor.setPosition(value["curPos"].toInt()); + ui->sqlEdit->setTextCursor(cursor); + } + + if (value.contains("db")) + { + dbCombo->setCurrentText(value["db"].toString()); + if (dbCombo->currentText().isEmpty() && dbCombo->count() > 0) + dbCombo->setCurrentIndex(0); + } + return true; +} + +void EditorWindow::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +Db* EditorWindow::getCurrentDb() +{ + return dbComboModel->getDb(dbCombo->currentIndex()); +} + +void EditorWindow::updateResultsDisplayMode() +{ + switch (resultsDisplayMode) + { + case EditorWindow::ResultsDisplayMode::SEPARATE_TAB: + { + // Remove old view + ui->resultsContainer->hide(); + ui->resultsContainer->layout()->removeWidget(ui->resultsFrame); + + // Add new view + ui->tabWidget->insertTab(1, ui->results, tr("Results")); + ui->resultsFrame->setParent(ui->results); + ui->results->layout()->addWidget(ui->resultsFrame); + break; + } + case EditorWindow::ResultsDisplayMode::BELOW_QUERY: + { + int currIdx = ui->tabWidget->currentIndex(); + + // Remove old view + ui->tabWidget->removeTab(1); + ui->results->layout()->removeWidget(ui->resultsFrame); + + // Add new view + ui->resultsContainer->show(); + ui->resultsFrame->setParent(ui->resultsContainer); + ui->resultsContainer->layout()->addWidget(ui->resultsFrame); + + // If results tab was selected before, switch to first tab + if (currIdx == 1) + { + ui->tabWidget->setCurrentIndex(0); + ui->dataView->setCurrentIndex(0); + ui->dataView->getGridView()->setFocus(); + } + break; + } + } +} + +void EditorWindow::createActions() +{ + // SQL editor toolbar + createAction(EXEC_QUERY, ICONS.EXEC_QUERY, tr("Execute query"), this, SLOT(execQuery()), ui->toolBar, ui->sqlEdit); + createAction(EXPLAIN_QUERY, ICONS.EXPLAIN_QUERY, tr("Explain query"), this, SLOT(explainQuery()), ui->toolBar, ui->sqlEdit); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::FORMAT_SQL)); + createAction(CLEAR_HISTORY, ICONS.CLEAR_HISTORY, tr("Clear execution history", "sql editor"), this, SLOT(clearHistory()), ui->toolBar); + ui->toolBar->addSeparator(); + createAction(EXPORT_RESULTS, ICONS.TABLE_EXPORT, tr("Export results", "sql editor"), this, SLOT(exportResults()), ui->toolBar); + ui->toolBar->addSeparator(); + createAction(CREATE_VIEW_FROM_QUERY, ICONS.VIEW_ADD, tr("Create view from query", "sql editor"), this, SLOT(createViewFromQuery()), ui->toolBar); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::SAVE_SQL_FILE)); + ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::OPEN_SQL_FILE)); + ui->toolBar->addSeparator(); + actionMap[CURRENT_DB] = ui->toolBar->addWidget(dbCombo); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(staticActions[RESULTS_IN_TAB]); + ui->toolBar->addAction(staticActions[RESULTS_BELOW]); + createAction(PREV_DB, tr("Previous database"), this, SLOT(prevDb()), this); + createAction(NEXT_DB, tr("Next database"), this, SLOT(nextDb()), this); + + // Other actions + createAction(SHOW_NEXT_TAB, tr("Show next tab", "sql editor"), this, SLOT(showNextTab()), this); + createAction(SHOW_PREV_TAB, tr("Show previous tab", "sql editor"), this, SLOT(showPrevTab()), this); + createAction(FOCUS_RESULTS_BELOW, tr("Focus results below", "sql editor"), this, SLOT(focusResultsBelow()), this); + createAction(FOCUS_EDITOR_ABOVE, tr("Focus SQL editor above", "sql editor"), this, SLOT(focusEditorAbove()), this); + + // Static action triggers + connect(staticActions[RESULTS_IN_TAB], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode())); + connect(staticActions[RESULTS_BELOW], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode())); +} + +void EditorWindow::createDbCombo() +{ + dbCombo = new QComboBox(this); + dbComboModel = new DbListModel(this); + dbComboModel->setCombo(dbCombo); + dbCombo->setModel(dbComboModel); + dbCombo->setEditable(false); + dbCombo->setFixedWidth(100); + connect(dbCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(dbChanged())); +} + +void EditorWindow::setupDefShortcuts() +{ + // Widget context + setShortcutContext({EXEC_QUERY, EXEC_QUERY, SHOW_NEXT_TAB, SHOW_PREV_TAB, FOCUS_RESULTS_BELOW, + FOCUS_EDITOR_ABOVE}, Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(EditorWindow, Action); +} + +void EditorWindow::selectCurrentQuery(bool fallBackToPreviousIfNecessary) +{ + Dialect dialect = Dialect::Sqlite3; + Db* db = getCurrentDb(); + if (db && db->isValid()) + dialect = db->getDialect(); + + QTextCursor cursor = ui->sqlEdit->textCursor(); + int pos = cursor.position(); + int queryStartPos; + QString contents = ui->sqlEdit->toPlainText(); + QString query = getQueryWithPosition(contents, pos, dialect, &queryStartPos); + TokenList tokens = Lexer::tokenize(query, dialect); + tokens.trim(); + tokens.trimRight(Token::OPERATOR, ";"); + + if (tokens.size() == 0 && fallBackToPreviousIfNecessary) + { + // Fallback + pos = contents.lastIndexOf(";", pos - 1); + if (pos > -1) + { + query = getQueryWithPosition(contents, pos, dialect, &queryStartPos); + tokens = Lexer::tokenize(query, dialect); + tokens.trim(); + tokens.trimRight(Token::OPERATOR, ";"); + } + } + + if (tokens.size() == 0) + { + qWarning() << "No tokens to select in EditorWindow::selectCurrentQuery()."; + return; + } + + cursor.clearSelection(); + cursor.setPosition(tokens.first()->start + queryStartPos); + cursor.setPosition(tokens.last()->end + 1 + queryStartPos, QTextCursor::KeepAnchor); + ui->sqlEdit->setTextCursor(cursor); +} + +void EditorWindow::updateShortcutTips() +{ + if (actionMap.contains(PREV_DB) && actionMap.contains(NEXT_DB)) + { + QString prevDbKey = actionMap[PREV_DB]->shortcut().toString(QKeySequence::NativeText); + QString nextDbKey = actionMap[NEXT_DB]->shortcut().toString(QKeySequence::NativeText); + dbCombo->setToolTip(tr("Active database (%1/%2)").arg(prevDbKey).arg(nextDbKey)); + } +} + +void EditorWindow::execQuery(bool explain) +{ + QString sql = getQueryToExecute(true); + resultsModel->setDb(getCurrentDb()); + resultsModel->setExplainMode(explain); + resultsModel->setQuery(sql); + ui->dataView->refreshData(); + updateState(); + + if (resultsDisplayMode == ResultsDisplayMode::SEPARATE_TAB) + { + ui->tabWidget->setCurrentIndex(1); + ui->dataView->setCurrentIndex(0); + ui->dataView->getGridView()->setFocus(); + } +} + +void EditorWindow::explainQuery() +{ + execQuery(true); +} + +void EditorWindow::dbChanged() +{ + Db* currentDb = getCurrentDb(); + ui->sqlEdit->setDb(currentDb); +} + +void EditorWindow::executionSuccessful() +{ + double secs = ((double)resultsModel->getExecutionTime()) / 1000; + QString time = QString::number(secs, 'f', 3); + notifyInfo(tr("Query finished in %2 second(s).").arg(time)); + + lastQueryHistoryId = CFG->addSqlHistory(resultsModel->getQuery(), resultsModel->getDb()->getName(), resultsModel->getExecutionTime(), 0); + + // If we added first history entry - resize dates column. + if (ui->historyList->model()->rowCount() == 1) + ui->historyList->resizeColumnToContents(1); + + Db* currentDb = getCurrentDb(); + if (currentDb && resultsModel->wasSchemaModified()) + DBTREE->refreshSchema(currentDb); + + lastSuccessfulQuery = resultsModel->getQuery(); + + updateState(); +} + +void EditorWindow::executionFailed(const QString &errorText) +{ + notifyError(errorText); + updateState(); +} + +void EditorWindow::totalRowsAndPagesAvailable() +{ + qint64 rowsReturned = resultsModel->getTotalRowsReturned(); + qint64 rowsAffected = resultsModel->getTotalRowsAffected(); + qint64 rows; + if (rowsReturned > 0) + rows = rowsReturned; + else + rows = rowsAffected; + + CFG->updateSqlHistory(lastQueryHistoryId, resultsModel->getQuery(), resultsModel->getDb()->getName(), resultsModel->getExecutionTime(), rows); +} + +void EditorWindow::prevDb() +{ + int idx = dbCombo->currentIndex() - 1; + if (idx < 0) + return; + + dbCombo->setCurrentIndex(idx); +} + +void EditorWindow::nextDb() +{ + int idx = dbCombo->currentIndex() + 1; + if (idx >= dbCombo->count()) + return; + + dbCombo->setCurrentIndex(idx); +} + +void EditorWindow::showNextTab() +{ + int tabIdx = ui->tabWidget->currentIndex(); + tabIdx++; + ui->tabWidget->setCurrentIndex(tabIdx); +} + +void EditorWindow::showPrevTab() +{ + int tabIdx = ui->tabWidget->currentIndex(); + tabIdx--; + ui->tabWidget->setCurrentIndex(tabIdx); +} + +void EditorWindow::focusResultsBelow() +{ + if (resultsDisplayMode != ResultsDisplayMode::BELOW_QUERY) + return; + + ui->dataView->setCurrentIndex(0); + ui->dataView->getGridView()->setFocus(); +} + +void EditorWindow::focusEditorAbove() +{ + if (resultsDisplayMode != ResultsDisplayMode::BELOW_QUERY) + return; + + ui->sqlEdit->setFocus(); +} + +void EditorWindow::historyEntrySelected(const QModelIndex& current, const QModelIndex& previous) +{ + UNUSED(previous); + QString sql = ui->historyList->model()->index(current.row(), 4).data().toString(); + ui->historyContents->setPlainText(sql); +} + +void EditorWindow::historyEntryActivated(const QModelIndex& current) +{ + QString sql = ui->historyList->model()->index(current.row(), 4).data().toString(); + ui->sqlEdit->setPlainText(sql); + ui->tabWidget->setCurrentIndex(0); +} + +void EditorWindow::clearHistory() +{ + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Clear execution history"), tr("Are you sure you want to erase the entire SQL execution history? " + "This cannot be undone.")); + if (res != QMessageBox::Yes) + return; + + CFG->clearSqlHistory(); +} + +void EditorWindow::exportResults() +{ + if (!ExportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot export, because no export plugin is loaded.")); + return; + } + + QString query = lastSuccessfulQuery.isEmpty() ? getQueryToExecute() : lastSuccessfulQuery; + QStringList queries = splitQueries(query, getCurrentDb()->getDialect(), false); + if (queries.size() == 0) + { + qWarning() << "No queries after split in EditorWindow::exportResults()"; + return; + } + + ExportDialog dialog(this); + dialog.setQueryMode(getCurrentDb(), queries.last().trimmed()); + dialog.exec(); +} + +void EditorWindow::createViewFromQuery() +{ + if (!getCurrentDb()) + { + notifyError(tr("No database selected in the SQL editor. Cannot create a view for unknown database.")); + return; + } + + QString sql = getQueryToExecute(true); + DbObjectDialogs dialogs(getCurrentDb()); + dialogs.addView(sql); +} + +void EditorWindow::updateState() +{ + bool executionInProgress = resultsModel->isExecutionInProgress(); + actionMap[CURRENT_DB]->setEnabled(!executionInProgress); + actionMap[EXEC_QUERY]->setEnabled(!executionInProgress); + actionMap[EXPLAIN_QUERY]->setEnabled(!executionInProgress); +} + +int qHash(EditorWindow::ActionGroup actionGroup) +{ + return static_cast(actionGroup); +} + + +bool EditorWindow::isUncommited() const +{ + return ui->dataView->isUncommited(); +} + +QString EditorWindow::getQuitUncommitedConfirmMessage() const +{ + return tr("Editor window \"%1\" has uncommited data.").arg(getMdiWindow()->windowTitle()); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h new file mode 100644 index 0000000..0052a74 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h @@ -0,0 +1,155 @@ +#ifndef EDITOR_H +#define EDITOR_H + +#include "db/db.h" +#include "mdichild.h" +#include "common/extactioncontainer.h" +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class EditorWindow; +} + +class SqlQueryModel; +class QComboBox; +class QActionGroup; +class DbListModel; +class QLabel; +class QLineEdit; +class ExtLineEdit; +class IntValidator; +class FormView; +class SqlQueryItem; +class SqlEditor; + +CFG_KEY_LIST(EditorWindow, QObject::tr("SQL editor window"), + CFG_KEY_ENTRY(EXEC_QUERY, Qt::Key_F9, QObject::tr("Execute query")) + CFG_KEY_ENTRY(EXPLAIN_QUERY, Qt::Key_F8, QObject::tr("Execute \"EXPLAIN\" query")) + CFG_KEY_ENTRY(PREV_DB, Qt::CTRL + Qt::Key_Up, QObject::tr("Switch current working database to previous on the list")) + CFG_KEY_ENTRY(NEXT_DB, Qt::CTRL + Qt::Key_Down, QObject::tr("Switch current working database to next on the list")) + CFG_KEY_ENTRY(SHOW_NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next editor tab")) + CFG_KEY_ENTRY(SHOW_PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous editor tab")) + CFG_KEY_ENTRY(FOCUS_RESULTS_BELOW, Qt::ALT + Qt::Key_PageDown, QObject::tr("Move keyboard input focus to the results view below")) + CFG_KEY_ENTRY(FOCUS_EDITOR_ABOVE, Qt::ALT + Qt::Key_PageUp, QObject::tr("Move keyboard input focus to the SQL editor above")) +) + +class GUI_API_EXPORT EditorWindow : public MdiChild +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum class ResultsDisplayMode + { + SEPARATE_TAB = 0, + BELOW_QUERY = 1 + }; + + enum Action + { + EXEC_QUERY, + EXPLAIN_QUERY, + RESULTS_IN_TAB, + RESULTS_BELOW, + CURRENT_DB, + NEXT_DB, + PREV_DB, + SHOW_NEXT_TAB, + SHOW_PREV_TAB, + FOCUS_RESULTS_BELOW, + FOCUS_EDITOR_ABOVE, + CLEAR_HISTORY, + EXPORT_RESULTS, + CREATE_VIEW_FROM_QUERY + }; + + enum ToolBar + { + TOOLBAR_MAIN + }; + + enum class ActionGroup + { + RESULTS_POSITIONING + }; + + explicit EditorWindow(QWidget *parent = 0); + EditorWindow(const EditorWindow& editor); + ~EditorWindow(); + + static void staticInit(); + static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_MAIN); + static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_MAIN); + static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_MAIN); + static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_MAIN); + + QSize sizeHint() const; + QAction* getAction(Action action); + QString getQueryToExecute(bool doSelectCurrentQuery = false); + bool setCurrentDb(Db* db); + void setContents(const QString& sql); + QString getContents() const; + void execute(); + QToolBar* getToolBar(int toolbar) const; + SqlEditor* getEditor() const; + bool isUncommited() const; + QString getQuitUncommitedConfirmMessage() const; + + protected: + void changeEvent(QEvent *e); + QVariant saveSession(); + bool restoreSession(const QVariant& sessionValue); + Icon* getIconNameForMdiWindow(); + QString getTitleForMdiWindow(); + Db* getCurrentDb(); + + private: + static void createStaticActions(); + static void loadTabsMode(); + + void init(); + void createActions(); + void createDbCombo(); + void setupDefShortcuts(); + void selectCurrentQuery(bool fallBackToPreviousIfNecessary = false); + void updateShortcutTips(); + + static ResultsDisplayMode resultsDisplayMode; + static QHash staticActions; + static QHash staticActionGroups; + + Ui::EditorWindow *ui = nullptr; + SqlQueryModel* resultsModel = nullptr; + QHash actionGroups; + QComboBox* dbCombo = nullptr; + DbListModel* dbComboModel = nullptr; + int sqlEditorNum = 1; + qint64 lastQueryHistoryId = 0; + QString lastSuccessfulQuery; + + private slots: + void execQuery(bool explain = false); + void explainQuery(); + void dbChanged(); + void executionSuccessful(); + void executionFailed(const QString& errorText); + void totalRowsAndPagesAvailable(); + void updateResultsDisplayMode(); + void prevDb(); + void nextDb(); + void showNextTab(); + void showPrevTab(); + void focusResultsBelow(); + void focusEditorAbove(); + void historyEntrySelected(const QModelIndex& current, const QModelIndex& previous); + void historyEntryActivated(const QModelIndex& current); + void clearHistory(); + void exportResults(); + void createViewFromQuery(); + void updateState(); +}; + +GUI_API_EXPORT int qHash(EditorWindow::ActionGroup action); + +#endif // EDITOR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui new file mode 100644 index 0000000..b0d598b --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui @@ -0,0 +1,137 @@ + + + EditorWindow + + + + 0 + 0 + 502 + 325 + + + + SQL editor + + + + 0 + + + + + + + + 0 + + + + Query + + + + + + Qt::Vertical + + + + Qt::CustomContextMenu + + + + + + 0 + + + + + + + + + + Results + + + + + + + 0 + + + + + QTabWidget::South + + + -1 + + + + + + + + + + + History + + + + + + Qt::Vertical + + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + + + true + + + + + + + + + + + + + DataView + QTabWidget +
    dataview.h
    + 1 +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    +
    + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp new file mode 100644 index 0000000..29163e3 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp @@ -0,0 +1,632 @@ +#include "functionseditor.h" +#include "ui_functionseditor.h" +#include "common/unused.h" +#include "common/utils.h" +#include "uiutils.h" +#include "functionseditormodel.h" +#include "services/pluginmanager.h" +#include "dbtree/dbtree.h" +#include "dbtree/dbtreemodel.h" +#include "dbtree/dbtreeitem.h" +#include "iconmanager.h" +#include "syntaxhighlighterplugin.h" +#include "sqlitesyntaxhighlighter.h" +#include "plugins/scriptingplugin.h" +#include "common/userinputfilter.h" +#include "selectabledbmodel.h" +#include "uiconfig.h" +#include +#include +#include + +// TODO handle plugin loading/unloading to update editor state + +FunctionsEditor::FunctionsEditor(QWidget *parent) : + MdiChild(parent), + ui(new Ui::FunctionsEditor) +{ + init(); +} + +FunctionsEditor::~FunctionsEditor() +{ + delete ui; +} + +bool FunctionsEditor::restoreSessionNextTime() +{ + return false; +} + +bool FunctionsEditor::restoreSession(const QVariant &sessionValue) +{ + UNUSED(sessionValue); + return true; +} + +Icon* FunctionsEditor::getIconNameForMdiWindow() +{ + return ICONS.FUNCTION; +} + +QString FunctionsEditor::getTitleForMdiWindow() +{ + return tr("SQL function editor"); +} + +void FunctionsEditor::createActions() +{ + createAction(COMMIT, ICONS.COMMIT, tr("Commit all function changes"), this, SLOT(commit()), ui->toolBar); + createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback all function changes"), this, SLOT(rollback()), ui->toolBar); + ui->toolBar->addSeparator(); + createAction(ADD, ICONS.NEW_FUNCTION, tr("Create new function"), this, SLOT(newFunction()), ui->toolBar); + createAction(DELETE, ICONS.DELETE_FUNCTION, tr("Delete selected function"), this, SLOT(deleteFunction()), ui->toolBar); + ui->toolBar->addSeparator(); + createAction(HELP, ICONS.HELP, tr("Custom SQL functions manual"), this, SLOT(help()), ui->toolBar); + + // Args toolbar + createAction(ARG_ADD, ICONS.INSERT_FN_ARG, tr("Add function argument"), this, SLOT(addFunctionArg()), ui->argsToolBar); + createAction(ARG_EDIT, ICONS.RENAME_FN_ARG, tr("Rename function argument"), this, SLOT(editFunctionArg()), ui->argsToolBar); + createAction(ARG_DEL, ICONS.DELETE_FN_ARG, tr("Delete function argument"), this, SLOT(delFunctionArg()), ui->argsToolBar); + ui->argsToolBar->addSeparator(); + createAction(ARG_MOVE_UP, ICONS.MOVE_UP, tr("Move function argument up"), this, SLOT(moveFunctionArgUp()), ui->argsToolBar); + createAction(ARG_MOVE_DOWN, ICONS.MOVE_DOWN, tr("Move function argument down"), this, SLOT(moveFunctionArgDown()), ui->argsToolBar); + +#ifdef Q_OS_MACX + QStyle *fusion = QStyleFactory::create("Fusion"); + ui->toolBar->setStyle(fusion); + ui->argsToolBar->setStyle(fusion); +#endif +} + +void FunctionsEditor::setupDefShortcuts() +{ +} + +QToolBar* FunctionsEditor::getToolBar(int toolbar) const +{ + UNUSED(toolbar); + return ui->toolBar; +} + +void FunctionsEditor::init() +{ + ui->setupUi(this); + clearEdits(); + ui->initCodeGroup->setVisible(false); + ui->finalCodeGroup->setVisible(false); + + setFont(CFG_UI.Fonts.SqlEditor.get()); + + model = new FunctionsEditorModel(this); + functionFilterModel = new QSortFilterProxyModel(this); + functionFilterModel->setSourceModel(model); + ui->list->setModel(functionFilterModel); + + dbListModel = new SelectableDbModel(this); + dbListModel->setSourceModel(DBTREE->getModel()); + ui->databasesList->setModel(dbListModel); + ui->databasesList->expandAll(); + + ui->typeCombo->addItem(tr("Scalar"), FunctionManager::ScriptFunction::SCALAR); + ui->typeCombo->addItem(tr("Aggregate"), FunctionManager::ScriptFunction::AGGREGATE); + + new UserInputFilter(ui->functionFilterEdit, this, SLOT(applyFilter(QString))); + functionFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + initActions(); + + connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(functionSelected(QItemSelection,QItemSelection))); + connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState())); + connect(ui->initCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified())); + connect(ui->mainCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified())); + connect(ui->finalCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified())); + connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateModified())); + connect(ui->undefArgsCheck, SIGNAL(clicked()), this, SLOT(updateModified())); + connect(ui->allDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified())); + connect(ui->selDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified())); + connect(ui->langCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified())); + connect(ui->typeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified())); + + connect(ui->argsList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateArgsState())); + connect(ui->argsList->model(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateModified())); + connect(ui->argsList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified())); + connect(ui->argsList->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateModified())); + connect(ui->argsList->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateModified())); + + connect(dbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified())); + connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant))); + + model->setData(FUNCTIONS->getAllScriptFunctions()); + + // Language plugins + foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins()) + scriptingPlugins[plugin->getLanguage()] = plugin; + + ui->langCombo->addItems(scriptingPlugins.keys()); + + // Syntax highlighting plugins + foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins()) + highlighterPlugins[plugin->getLanguageName()] = plugin; + + updateState(); +} + +int FunctionsEditor::getCurrentFunctionRow() const +{ + QModelIndexList idxList = ui->list->selectionModel()->selectedIndexes(); + if (idxList.size() == 0) + return -1; + + return idxList.first().row(); +} + +void FunctionsEditor::functionDeselected(int row) +{ + model->setName(row, ui->nameEdit->text()); + model->setLang(row, ui->langCombo->currentText()); + model->setType(row, getCurrentFunctionType()); + model->setUndefinedArgs(row, ui->undefArgsCheck->isChecked()); + model->setAllDatabases(row, ui->allDatabasesRadio->isChecked()); + model->setCode(row, ui->mainCodeEdit->toPlainText()); + model->setModified(row, currentModified); + + if (model->isAggregate(row)) + { + model->setInitCode(row, ui->initCodeEdit->toPlainText()); + model->setFinalCode(row, ui->finalCodeEdit->toPlainText()); + } + else + { + model->setInitCode(row, QString::null); + model->setFinalCode(row, QString::null); + } + + if (!ui->undefArgsCheck->isChecked()) + model->setArguments(row, getCurrentArgList()); + + if (ui->selDatabasesRadio->isChecked()) + model->setDatabases(row, getCurrentDatabases()); + + model->validateNames(); +} + +void FunctionsEditor::functionSelected(int row) +{ + updatesForSelection = true; + ui->nameEdit->setText(model->getName(row)); + ui->initCodeEdit->setPlainText(model->getInitCode(row)); + ui->mainCodeEdit->setPlainText(model->getCode(row)); + ui->finalCodeEdit->setPlainText(model->getFinalCode(row)); + ui->undefArgsCheck->setChecked(model->getUndefinedArgs(row)); + ui->langCombo->setCurrentText(model->getLang(row)); + + // Arguments + ui->argsList->clear(); + QListWidgetItem* item = nullptr; + foreach (const QString& arg, model->getArguments(row)) + { + item = new QListWidgetItem(arg); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->argsList->addItem(item); + } + + // Databases + dbListModel->setDatabases(model->getDatabases(row)); + ui->databasesList->expandAll(); + + if (model->getAllDatabases(row)) + ui->allDatabasesRadio->setChecked(true); + else + ui->selDatabasesRadio->setChecked(true); + + // Type + FunctionManager::ScriptFunction::Type type = model->getType(row); + for (int i = 0; i < ui->typeCombo->count(); i++) + { + if (ui->typeCombo->itemData(i).toInt() == type) + { + ui->typeCombo->setCurrentIndex(i); + break; + } + } + + updatesForSelection = false; + currentModified = false; + + updateCurrentFunctionState(); +} + +void FunctionsEditor::clearEdits() +{ + ui->nameEdit->setText(QString::null); + ui->mainCodeEdit->setPlainText(QString::null); + ui->langCombo->setCurrentText(QString::null); + ui->undefArgsCheck->setChecked(true); + ui->argsList->clear(); + ui->allDatabasesRadio->setChecked(true); + ui->typeCombo->setCurrentIndex(0); + ui->langCombo->setCurrentIndex(-1); +} + +void FunctionsEditor::selectFunction(int row) +{ + if (!model->isValidRowIndex(row)) + return; + + ui->list->selectionModel()->setCurrentIndex(model->index(row), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); +} + +void FunctionsEditor::setFont(const QFont& font) +{ + ui->initCodeEdit->setFont(font); + ui->mainCodeEdit->setFont(font); + ui->finalCodeEdit->setFont(font); +} + +QModelIndex FunctionsEditor::getSelectedArg() const +{ + QModelIndexList indexes = ui->argsList->selectionModel()->selectedIndexes(); + if (indexes.size() == 0 || !indexes.first().isValid()) + return QModelIndex(); + + return indexes.first(); + +} + +QStringList FunctionsEditor::getCurrentArgList() const +{ + QStringList currArgList; + for (int row = 0; row < ui->argsList->model()->rowCount(); row++) + currArgList << ui->argsList->item(row)->text(); + + return currArgList; +} + +QStringList FunctionsEditor::getCurrentDatabases() const +{ + return dbListModel->getDatabases(); +} + +FunctionManager::ScriptFunction::Type FunctionsEditor::getCurrentFunctionType() const +{ + int intValue = ui->typeCombo->itemData(ui->typeCombo->currentIndex()).toInt(); + return static_cast(intValue); +} + +void FunctionsEditor::commit() +{ + int row = getCurrentFunctionRow(); + if (model->isValidRowIndex(row)) + functionDeselected(row); + + QList functions = model->generateFunctions(); + + FUNCTIONS->setScriptFunctions(functions); + model->clearModified(); + currentModified = false; + + if (model->isValidRowIndex(row)) + selectFunction(row); + + updateState(); +} + +void FunctionsEditor::rollback() +{ + int selectedBefore = getCurrentFunctionRow(); + + model->setData(FUNCTIONS->getAllScriptFunctions()); + currentModified = false; + clearEdits(); + + if (model->isValidRowIndex(selectedBefore)) + selectFunction(selectedBefore); + + updateState(); +} + +void FunctionsEditor::newFunction() +{ + if (ui->langCombo->currentIndex() == -1 && ui->langCombo->count() > 0) + ui->langCombo->setCurrentIndex(0); + + FunctionManager::ScriptFunction* func = new FunctionManager::ScriptFunction(); + func->name = generateUniqueName("function", model->getFunctionNames()); + + if (ui->langCombo->currentIndex() > -1) + func->lang = ui->langCombo->currentText(); + + model->addFunction(func); + + selectFunction(model->rowCount() - 1); +} + +void FunctionsEditor::deleteFunction() +{ + int row = getCurrentFunctionRow(); + model->deleteFunction(row); + clearEdits(); + + row = getCurrentFunctionRow(); + if (model->isValidRowIndex(row)) + functionSelected(row); + + updateState(); +} + +void FunctionsEditor::updateModified() +{ + if (updatesForSelection) + return; + + int row = getCurrentFunctionRow(); + if (model->isValidRowIndex(row)) + { + bool nameDiff = model->getName(row) != ui->nameEdit->text(); + bool codeDiff = model->getCode(row) != ui->mainCodeEdit->toPlainText(); + bool initCodeDiff = model->getInitCode(row) != ui->initCodeEdit->toPlainText(); + bool finalCodeDiff = model->getFinalCode(row) != ui->finalCodeEdit->toPlainText(); + bool langDiff = model->getLang(row) != ui->langCombo->currentText(); + bool undefArgsDiff = model->getUndefinedArgs(row) != ui->undefArgsCheck->isChecked(); + bool allDatabasesDiff = model->getAllDatabases(row) != ui->allDatabasesRadio->isChecked(); + bool argDiff = getCurrentArgList() != model->getArguments(row); + bool dbDiff = getCurrentDatabases().toSet() != model->getDatabases(row).toSet(); // QSet to ignore order + bool typeDiff = model->getType(row) != getCurrentFunctionType(); + + currentModified = (nameDiff || codeDiff || typeDiff || langDiff || undefArgsDiff || allDatabasesDiff || argDiff || dbDiff || + initCodeDiff || finalCodeDiff); + } + + updateCurrentFunctionState(); +} + +void FunctionsEditor::updateState() +{ + bool modified = model->isModified() || currentModified; + bool valid = model->isValid(); + + actionMap[COMMIT]->setEnabled(modified && valid); + actionMap[ROLLBACK]->setEnabled(modified); + actionMap[DELETE]->setEnabled(ui->list->selectionModel()->selectedIndexes().size() > 0); +} + +void FunctionsEditor::updateCurrentFunctionState() +{ + int row = getCurrentFunctionRow(); + bool validRow = model->isValidRowIndex(row); + ui->rightWidget->setEnabled(validRow); + if (!validRow) + { + setValidState(ui->langCombo, true); + setValidState(ui->nameEdit, true); + setValidState(ui->mainCodeEdit, true); + setValidState(ui->finalCodeEdit, true); + return; + } + + QString name = ui->nameEdit->text(); + bool nameOk = model->isAllowedName(row, name) && !name.trimmed().isEmpty(); + setValidState(ui->nameEdit, nameOk, tr("Enter a non-empty, unique name of the function.")); + + bool langOk = ui->langCombo->currentIndex() >= 0; + ui->initCodeGroup->setEnabled(langOk); + ui->mainCodeGroup->setEnabled(langOk); + ui->finalCodeGroup->setEnabled(langOk); + ui->argsGroup->setEnabled(langOk); + ui->databasesGroup->setEnabled(langOk); + ui->nameEdit->setEnabled(langOk); + ui->nameLabel->setEnabled(langOk); + ui->typeCombo->setEnabled(langOk); + ui->typeLabel->setEnabled(langOk); + setValidState(ui->langCombo, langOk, tr("Pick the implementation language.")); + + bool aggregate = getCurrentFunctionType() == FunctionManager::ScriptFunction::AGGREGATE; + ui->initCodeGroup->setVisible(aggregate); + ui->mainCodeGroup->setTitle(aggregate ? tr("Per step code:") : tr("Function implementation code:")); + ui->finalCodeGroup->setVisible(aggregate); + + ui->databasesList->setEnabled(ui->selDatabasesRadio->isChecked()); + + bool codeOk = !ui->mainCodeEdit->toPlainText().trimmed().isEmpty(); + setValidState(ui->mainCodeEdit, codeOk, tr("Enter a non-empty implementation code.")); + + bool finalCodeOk = true; + if (aggregate) + finalCodeOk = !ui->finalCodeEdit->toPlainText().trimmed().isEmpty(); + + setValidState(ui->finalCodeEdit, finalCodeOk); + + // Syntax highlighter + QString lang = ui->langCombo->currentText(); + if (lang != currentHighlighterLang) + { + QSyntaxHighlighter* highlighter = nullptr; + if (currentMainHighlighter) + { + // A pointers swap with local var - this is necessary, cause deleting highlighter + // triggers textChanged on QPlainTextEdit, which then calls this method, + // so it becomes an infinite recursion with deleting the same pointer. + // We set the pointer to null first, then delete it. That way it's safe. + highlighter = currentMainHighlighter; + currentMainHighlighter = nullptr; + delete highlighter; + } + + if (currentFinalHighlighter) + { + highlighter = currentFinalHighlighter; + currentFinalHighlighter = nullptr; + delete highlighter; + } + + if (currentInitHighlighter) + { + highlighter = currentInitHighlighter; + currentInitHighlighter = nullptr; + delete highlighter; + } + + if (langOk && highlighterPlugins.contains(lang)) + { + currentInitHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->initCodeEdit); + currentMainHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->mainCodeEdit); + currentFinalHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->finalCodeEdit); + } + + currentHighlighterLang = lang; + } + + updateArgsState(); + model->setValid(row, langOk && codeOk && finalCodeOk && nameOk); + updateState(); +} + +void FunctionsEditor::functionSelected(const QItemSelection& selected, const QItemSelection& deselected) +{ + int deselCnt = deselected.indexes().size(); + int selCnt = selected.indexes().size(); + + if (deselCnt > 0) + functionDeselected(deselected.indexes().first().row()); + + if (selCnt > 0) + functionSelected(selected.indexes().first().row()); + + if (deselCnt > 0 && selCnt == 0) + { + currentModified = false; + clearEdits(); + } +} + +void FunctionsEditor::addFunctionArg() +{ + QListWidgetItem* item = new QListWidgetItem(tr("argument", "new function argument name in function editor window")); + item->setFlags(item->flags () | Qt::ItemIsEditable); + ui->argsList->addItem(item); + + QModelIndex idx = ui->argsList->model()->index(ui->argsList->model()->rowCount() - 1, 0); + ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); + + ui->argsList->editItem(item); +} + +void FunctionsEditor::editFunctionArg() +{ + QModelIndex selected = getSelectedArg(); + if (!selected.isValid()) + return; + + int row = selected.row(); + QListWidgetItem* item = ui->argsList->item(row); + ui->argsList->editItem(item); +} + +void FunctionsEditor::delFunctionArg() +{ + QModelIndex selected = getSelectedArg(); + if (!selected.isValid()) + return; + + int row = selected.row(); + delete ui->argsList->takeItem(row); +} + +void FunctionsEditor::moveFunctionArgUp() +{ + QModelIndex selected = getSelectedArg(); + if (!selected.isValid()) + return; + + int row = selected.row(); + if (row <= 0) + return; + + ui->argsList->insertItem(row - 1, ui->argsList->takeItem(row)); + + QModelIndex idx = ui->argsList->model()->index(row - 1, 0); + ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); +} + +void FunctionsEditor::moveFunctionArgDown() +{ + QModelIndex selected = getSelectedArg(); + if (!selected.isValid()) + return; + + int row = selected.row(); + if (row >= ui->argsList->model()->rowCount() - 1) + return; + + ui->argsList->insertItem(row + 1, ui->argsList->takeItem(row)); + + QModelIndex idx = ui->argsList->model()->index(row + 1, 0); + ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); +} + +void FunctionsEditor::updateArgsState() +{ + bool argsEnabled = !ui->undefArgsCheck->isChecked(); + QModelIndexList indexes = ui->argsList->selectionModel()->selectedIndexes(); + bool argSelected = indexes.size() > 0; + + bool canMoveUp = false; + bool canMoveDown = false; + if (argSelected) + { + canMoveUp = indexes.first().row() > 0; + canMoveDown = (indexes.first().row() + 1) < ui->argsList->count(); + } + + actionMap[ARG_ADD]->setEnabled(argsEnabled); + actionMap[ARG_EDIT]->setEnabled(argsEnabled && argSelected); + actionMap[ARG_DEL]->setEnabled(argsEnabled && argSelected); + actionMap[ARG_MOVE_UP]->setEnabled(argsEnabled && canMoveUp); + actionMap[ARG_MOVE_DOWN]->setEnabled(argsEnabled && canMoveDown); + ui->argsList->setEnabled(argsEnabled); +} + +void FunctionsEditor::applyFilter(const QString& value) +{ + // Remembering old selection, clearing it and restoring afterwards is a workaround for a problem, + // which causees application to crash, when the item was selected, but after applying filter string, + // item was about to disappear. + // This must have something to do with the underlying model (FunctionsEditorModel) implementation, + // but for now I don't really know what is that. + // I have tested simple Qt application with the same routine, but the underlying model was QStandardItemModel + // and everything worked fine. + int row = getCurrentFunctionRow(); + ui->list->selectionModel()->clearSelection(); + + functionFilterModel->setFilterFixedString(value); + + selectFunction(row); +} + +void FunctionsEditor::help() +{ + static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions"); + QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode)); +} + +void FunctionsEditor::changeFont(const QVariant& font) +{ + setFont(font.value()); +} + +QVariant FunctionsEditor::saveSession() +{ + return QVariant(); +} + + +bool FunctionsEditor::isUncommited() const +{ + return model->isModified(); +} + +QString FunctionsEditor::getQuitUncommitedConfirmMessage() const +{ + return tr("Functions editor window has uncommited modifications."); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h new file mode 100644 index 0000000..0fa496e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h @@ -0,0 +1,109 @@ +#ifndef FUNCTIONSEDITOR_H +#define FUNCTIONSEDITOR_H + +#include "mdichild.h" +#include "common/extactioncontainer.h" +#include "services/config.h" +#include "services/functionmanager.h" +#include +#include + +namespace Ui { +class FunctionsEditor; +} + +class FunctionsEditorModel; +class ScriptingPlugin; +class SyntaxHighlighterPlugin; +class DbTreeItem; +class QTreeWidgetItem; +class QSyntaxHighlighter; +class SelectableDbModel; + +class GUI_API_EXPORT FunctionsEditor : public MdiChild +{ + Q_OBJECT + + public: + enum Action + { + COMMIT, + ROLLBACK, + ADD, + DELETE, + ARG_ADD, + ARG_EDIT, + ARG_DEL, + ARG_MOVE_UP, + ARG_MOVE_DOWN, + HELP + }; + + enum ToolBar + { + TOOLBAR + }; + + explicit FunctionsEditor(QWidget *parent = 0); + ~FunctionsEditor(); + + bool restoreSessionNextTime(); + bool isUncommited() const; + QString getQuitUncommitedConfirmMessage() const; + + protected: + QVariant saveSession(); + bool restoreSession(const QVariant &sessionValue); + Icon* getIconNameForMdiWindow(); + QString getTitleForMdiWindow(); + void createActions(); + void setupDefShortcuts(); + QToolBar* getToolBar(int toolbar) const; + + private: + void init(); + int getCurrentFunctionRow() const; + void functionDeselected(int row); + void functionSelected(int row); + void clearEdits(); + void selectFunction(int row); + void setFont(const QFont& font); + QModelIndex getSelectedArg() const; + QStringList getCurrentArgList() const; + QStringList getCurrentDatabases() const; + FunctionManager::ScriptFunction::Type getCurrentFunctionType() const; + + Ui::FunctionsEditor *ui = nullptr; + FunctionsEditorModel* model = nullptr; + QSortFilterProxyModel* functionFilterModel = nullptr; + bool currentModified = false; + QHash scriptingPlugins; + QHash highlighterPlugins; + SelectableDbModel* dbListModel = nullptr; + QString currentHighlighterLang; + QSyntaxHighlighter* currentMainHighlighter = nullptr; + QSyntaxHighlighter* currentFinalHighlighter = nullptr; + QSyntaxHighlighter* currentInitHighlighter = nullptr; + bool updatesForSelection = false; + + private slots: + void commit(); + void rollback(); + void newFunction(); + void deleteFunction(); + void updateModified(); + void updateState(); + void updateCurrentFunctionState(); + void functionSelected(const QItemSelection& selected, const QItemSelection& deselected); + void addFunctionArg(); + void editFunctionArg(); + void delFunctionArg(); + void moveFunctionArgUp(); + void moveFunctionArgDown(); + void updateArgsState(); + void applyFilter(const QString& value); + void help(); + void changeFont(const QVariant& font); +}; + +#endif // FUNCTIONSEDITOR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui new file mode 100644 index 0000000..d5d5015 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui @@ -0,0 +1,346 @@ + + + FunctionsEditor + + + + 0 + 0 + 816 + 621 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + 1 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 150 + 0 + + + + Filter funtions + + + true + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ScrollPerPixel + + + + + + + + + 4 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Function name: + + + + + + + + + + Implementation language: + + + + + + + + + + Type: + + + + + + + + + + + 4 + 0 + + + + Input arguments + + + + + + Undefined + + + + + + + true + + + QAbstractItemView::InternalMove + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 1 + 0 + + + + Databases + + + + + + Register in all databases + + + + + + + Register in following databases: + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + 0 + + + + + + + + + + + + 0 + 2 + + + + Initialization code: + + + + + + + + + + + 0 + 2 + + + + Function implementation code: + + + + + + + + + + + 0 + 2 + + + + Final step implementation code: + + + + + + + + + + + + + + + + + + + + functionFilterEdit + list + nameEdit + typeCombo + langCombo + undefArgsCheck + argsList + allDatabasesRadio + selDatabasesRadio + databasesList + initCodeEdit + mainCodeEdit + finalCodeEdit + + + + diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp new file mode 100644 index 0000000..cf7efdf --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp @@ -0,0 +1,348 @@ +#include "functionseditormodel.h" +#include "common/strhash.h" +#include "common/unused.h" +#include "services/pluginmanager.h" +#include "plugins/scriptingplugin.h" +#include "icon.h" +#include + +#define SETTER(X, Y) \ + if (!isValidRowIndex(row) || X == Y) \ + return; \ + \ + X = Y; \ + emitDataChanged(row); + +#define GETTER(X, Y) \ + if (!isValidRowIndex(row)) \ + return Y; \ + \ + return X; + +FunctionsEditorModel::FunctionsEditorModel(QObject *parent) : + QAbstractListModel(parent) +{ + init(); +} + +void FunctionsEditorModel::clearModified() +{ + beginResetModel(); + foreach (Function* func, functionList) + func->modified = false; + + listModified = false; + originalFunctionList = functionList; + + endResetModel(); +} + +bool FunctionsEditorModel::isModified() const +{ + if (functionList != originalFunctionList) + return true; + + foreach (Function* func, functionList) + { + if (func->modified) + return true; + } + return false; +} + +bool FunctionsEditorModel::isModified(int row) const +{ + GETTER(functionList[row]->modified, false); +} + +void FunctionsEditorModel::setModified(int row, bool modified) +{ + SETTER(functionList[row]->modified, modified); +} + +bool FunctionsEditorModel::isValid() const +{ + foreach (Function* func, functionList) + { + if (!func->valid) + return false; + } + return true; +} + +bool FunctionsEditorModel::isValid(int row) const +{ + GETTER(functionList[row]->valid, false); +} + +void FunctionsEditorModel::setValid(int row, bool valid) +{ + SETTER(functionList[row]->valid, valid); +} + +void FunctionsEditorModel::setCode(int row, const QString& code) +{ + SETTER(functionList[row]->data.code, code); +} + +QString FunctionsEditorModel::getCode(int row) const +{ + GETTER(functionList[row]->data.code, QString::null); +} + +void FunctionsEditorModel::setFinalCode(int row, const QString& code) +{ + SETTER(functionList[row]->data.finalCode, code); +} + +QString FunctionsEditorModel::getFinalCode(int row) const +{ + GETTER(functionList[row]->data.finalCode, QString::null); +} + +void FunctionsEditorModel::setInitCode(int row, const QString& code) +{ + SETTER(functionList[row]->data.initCode, code); +} + +QString FunctionsEditorModel::getInitCode(int row) const +{ + GETTER(functionList[row]->data.initCode, QString::null); +} + +void FunctionsEditorModel::setName(int row, const QString& newName) +{ + SETTER(functionList[row]->data.name, newName); +} + +QString FunctionsEditorModel::getName(int row) const +{ + GETTER(functionList[row]->data.name, QString::null); +} + +void FunctionsEditorModel::setLang(int row, const QString& lang) +{ + SETTER(functionList[row]->data.lang, lang); +} + +QString FunctionsEditorModel::getLang(int row) const +{ + GETTER(functionList[row]->data.lang, QString::null); +} + +bool FunctionsEditorModel::getUndefinedArgs(int row) const +{ + GETTER(functionList[row]->data.undefinedArgs, true); +} + +void FunctionsEditorModel::setUndefinedArgs(int row, bool value) +{ + SETTER(functionList[row]->data.undefinedArgs, value); +} + +bool FunctionsEditorModel::getAllDatabases(int row) const +{ + GETTER(functionList[row]->data.allDatabases, true); +} + +void FunctionsEditorModel::setAllDatabases(int row, bool value) +{ + SETTER(functionList[row]->data.allDatabases, value); +} + +FunctionManager::ScriptFunction::Type FunctionsEditorModel::getType(int row) const +{ + GETTER(functionList[row]->data.type, FunctionManager::ScriptFunction::SCALAR); +} + +void FunctionsEditorModel::setType(int row, FunctionManager::ScriptFunction::Type type) +{ + SETTER(functionList[row]->data.type, type); +} + +bool FunctionsEditorModel::isAggregate(int row) const +{ + GETTER(functionList[row]->data.type == FunctionManager::ScriptFunction::AGGREGATE, false); +} + +bool FunctionsEditorModel::isScalar(int row) const +{ + GETTER(functionList[row]->data.type == FunctionManager::ScriptFunction::SCALAR, false); +} + +QStringList FunctionsEditorModel::getArguments(int row) const +{ + GETTER(functionList[row]->data.arguments, QStringList()); +} + +void FunctionsEditorModel::setArguments(int row, const QStringList& value) +{ + SETTER(functionList[row]->data.arguments, value); +} + +QStringList FunctionsEditorModel::getDatabases(int row) const +{ + GETTER(functionList[row]->data.databases, QStringList()); +} + +void FunctionsEditorModel::setDatabases(int row, const QStringList& value) +{ + SETTER(functionList[row]->data.databases, value); +} + +void FunctionsEditorModel::setData(const QList& functions) +{ + beginResetModel(); + + for (Function* functionPtr : functionList) + delete functionPtr; + + functionList.clear(); + + foreach (FunctionManager::ScriptFunction* func, functions) + functionList << new Function(func); + + listModified = false; + originalFunctionList = functionList; + + endResetModel(); +} + +void FunctionsEditorModel::addFunction(FunctionManager::ScriptFunction* function) +{ + int row = functionList.size(); + + beginInsertRows(QModelIndex(), row, row); + + functionList << new Function(function); + listModified = true; + + endInsertRows(); +} + +void FunctionsEditorModel::deleteFunction(int row) +{ + if (!isValidRowIndex(row)) + return; + + beginRemoveRows(QModelIndex(), row, row); + + delete functionList[row]; + functionList.removeAt(row); + + listModified = true; + + endRemoveRows(); +} + +QList FunctionsEditorModel::generateFunctions() const +{ + QList results; + + foreach (Function* func, functionList) + results << new FunctionManager::ScriptFunction(func->data); + + return results; +} + +QStringList FunctionsEditorModel::getFunctionNames() const +{ + QStringList names; + foreach (Function* func, functionList) + names << func->data.name; + + return names; +} + +void FunctionsEditorModel::validateNames() +{ + StrHash> counter; + + int row = 0; + foreach (Function* func, functionList) + { + func->valid &= true; + counter[func->data.name] << row++; + } + + QHashIterator> cntIt = counter.iterator(); + while (cntIt.hasNext()) + { + cntIt.next(); + if (cntIt.value().size() > 1) + { + foreach (int cntRow, cntIt.value()) + setValid(cntRow, false); + } + } + + QModelIndex idx; + for (int i = 0; i < functionList.size(); i++) + { + idx = index(i); + emit dataChanged(idx, idx); + } +} + +bool FunctionsEditorModel::isAllowedName(int rowToSkip, const QString& nameToValidate) +{ + QStringList names = getFunctionNames(); + names.removeAt(rowToSkip); + return !names.contains(nameToValidate, Qt::CaseInsensitive); +} + +int FunctionsEditorModel::rowCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return functionList.size(); +} + +QVariant FunctionsEditorModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || !isValidRowIndex(index.row())) + return QVariant(); + + if (role == Qt::DisplayRole) + { + Function* fn = functionList[index.row()]; + return fn->data.toString(); + } + + if (role == Qt::DecorationRole && langToIcon.contains(functionList[index.row()]->data.lang)) + { + QIcon icon = langToIcon[functionList[index.row()]->data.lang]; + if (!functionList[index.row()]->valid) + icon = Icon::merge(icon, Icon::ERROR); + + return icon; + } + + return QVariant(); +} + +void FunctionsEditorModel::init() +{ + foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins()) + langToIcon[plugin->getLanguage()] = QIcon(plugin->getIconPath()); +} + +bool FunctionsEditorModel::isValidRowIndex(int row) const +{ + return (row >= 0 && row < functionList.size()); +} + +void FunctionsEditorModel::emitDataChanged(int row) +{ + QModelIndex idx = index(row); + emit dataChanged(idx, idx); +} + +FunctionsEditorModel::Function::Function() +{ +} + +FunctionsEditorModel::Function::Function(FunctionManager::ScriptFunction* other) +{ + data = FunctionManager::ScriptFunction(*other); + originalName = data.name; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h new file mode 100644 index 0000000..79f073f --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h @@ -0,0 +1,98 @@ +#ifndef FUNCTIONSEDITORMODEL_H +#define FUNCTIONSEDITORMODEL_H + +#include "services/config.h" +#include "services/functionmanager.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class GUI_API_EXPORT FunctionsEditorModel : public QAbstractListModel +{ + Q_OBJECT + + public: + using QAbstractItemModel::setData; + + enum Role + { + CODE = 1000, + MODIFIED = 1001, + VALID = 1002, + TYPE = 1003 + }; + + explicit FunctionsEditorModel(QObject *parent = 0); + + void clearModified(); + bool isModified() const; + bool isModified(int row) const; + void setModified(int row, bool modified); + bool isValid() const; + bool isValid(int row) const; + void setValid(int row, bool valid); + void setCode(int row, const QString& code); + QString getCode(int row) const; + void setFinalCode(int row, const QString& code); + QString getFinalCode(int row) const; + void setInitCode(int row, const QString& code); + QString getInitCode(int row) const; + void setName(int row, const QString& newName); + QString getName(int row) const; + void setLang(int row, const QString& lang); + QString getLang(int row) const; + QStringList getDatabases(int row) const; + void setDatabases(int row, const QStringList& value); + QStringList getArguments(int row) const; + void setArguments(int row, const QStringList& value); + FunctionManager::ScriptFunction::Type getType(int row) const; + void setType(int row, FunctionManager::ScriptFunction::Type type); + bool isAggregate(int row) const; + bool isScalar(int row) const; + bool getUndefinedArgs(int row) const; + void setUndefinedArgs(int row, bool value); + bool getAllDatabases(int row) const; + void setAllDatabases(int row, bool value); + void setData(const QList& functions); + void addFunction(FunctionManager::ScriptFunction* function); + void deleteFunction(int row); + QList generateFunctions() const; + QStringList getFunctionNames() const; + void validateNames(); + bool isAllowedName(int rowToSkip, const QString& nameToValidate); + bool isValidRowIndex(int row) const; + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role) const; + + private: + struct Function + { + Function(); + Function(FunctionManager::ScriptFunction* other); + + FunctionManager::ScriptFunction data; + bool modified = false; + bool valid = true; + QString originalName; + }; + + void init(); + void emitDataChanged(int row); + + QList functionList; + + /** + * @brief List of function pointers before modifications. + * + * This list is kept to check for modifications in the overall list of functions. + * Pointers on this list may be already deleted, so don't use them! + * It's only used to compare list of pointers to functionList, so it can tell you + * if the list was modified in regards of adding or deleting functions. + */ + QList originalFunctionList; + QHash langToIcon; + bool listModified = false; +}; + +#endif // FUNCTIONSEDITORMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp new file mode 100644 index 0000000..850d8a7 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp @@ -0,0 +1,482 @@ +#include "tableconstraintsmodel.h" +#include "iconmanager.h" +#include "common/utils_sql.h" +#include "common/unused.h" +#include +#include + +TableConstraintsModel::TableConstraintsModel(QObject *parent) : + QAbstractTableModel(parent) +{ +} + +int TableConstraintsModel::rowCount(const QModelIndex& parent) const +{ + UNUSED(parent); + if (createTable.isNull()) + return 0; + + return createTable->constraints.size(); +} + +int TableConstraintsModel::columnCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return 3; +} + +QVariant TableConstraintsModel::data(const QModelIndex& index, int role) const +{ + if (createTable.isNull()) + return QVariant(); + + SqliteCreateTable::Constraint* constr = createTable->constraints[index.row()]; + switch (getColumn(index.column())) + { + case Columns::TYPE: + { + if (role == Qt::DisplayRole) + return getTypeLabel(constr->type); + + if (role == Qt::DecorationRole) + return getTypeIcon(constr->type); + + break; + } + case Columns::NAME: + { + if (role == Qt::DisplayRole) + return stripObjName(constr->name, createTable->dialect); + + break; + } + case Columns::DETAILS: + { + if (role == Qt::DisplayRole) + return getDetails(constr); + + break; + } + } + return QVariant(); +} + +QVariant TableConstraintsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QAbstractTableModel::headerData(section, orientation, role); + + if (orientation == Qt::Vertical) + return section + 1; + + switch (getColumn(section)) + { + case TableConstraintsModel::Columns::TYPE: + return tr("Type", "table constraints"); + case TableConstraintsModel::Columns::DETAILS: + return tr("Details", "table constraints"); + case TableConstraintsModel::Columns::NAME: + return tr("Name", "table constraints"); + } + return QVariant(); +} + +Qt::DropActions TableConstraintsModel::supportedDropActions() const +{ + return Qt::MoveAction; +} + +Qt::DropActions TableConstraintsModel::supportedDragActions() const +{ + return Qt::CopyAction|Qt::MoveAction; +} + +QStringList TableConstraintsModel::mimeTypes() const +{ + return {mimeType}; +} + +QMimeData* TableConstraintsModel::mimeData(const QModelIndexList& indexes) const +{ + if (indexes.size() < 1) + return nullptr; + + QModelIndex idx = indexes.first(); + + QMimeData *data = new QMimeData(); + + QByteArray output; + QDataStream stream(&output, QIODevice::WriteOnly); + + stream << idx.row(); + data->setData(mimeType, output); + + return data; +} + +bool TableConstraintsModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +{ + UNUSED(action); + UNUSED(row); + UNUSED(column); + UNUSED(parent); + + if (!data) + return false; + + if (!data->hasFormat(mimeType)) + return false; + + return true; +} + +bool TableConstraintsModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + UNUSED(column); + + if (action == Qt::IgnoreAction) + return true; + + if (!data) + return false; + + if (!data->hasFormat(mimeType)) + return false; + + if (action != Qt::MoveAction) + return false; + + if (row < 0) + { + if (!parent.isValid() && !createTable.isNull()) + row = createTable->constraints.size(); + else + row = parent.row(); + } + + if (row < 0) + return false; + + QByteArray byteData = data->data(mimeType); + QDataStream stream(&byteData, QIODevice::ReadOnly); + int oldRow; + stream >> oldRow; + + moveConstraintColumnTo(oldRow, row); + return true; +} + +Qt::ItemFlags TableConstraintsModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags defFlags = QAbstractItemModel::flags(index); + if (!index.isValid()) + return defFlags|Qt::ItemIsDropEnabled; + + return defFlags|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled; +} + +bool TableConstraintsModel::isModified() const +{ + return modified; +} + +void TableConstraintsModel::setCreateTable(SqliteCreateTable* value) +{ + beginResetModel(); + createTable = value; + endResetModel(); + modified = false; + emit modifiyStateChanged(); +} + +SqliteCreateTable::Constraint* TableConstraintsModel::getConstraint(int constrIdx) const +{ + if (createTable.isNull()) + return nullptr; + + return createTable->constraints[constrIdx]; +} + +void TableConstraintsModel::replaceConstraint(int constrIdx, SqliteCreateTable::Constraint* constr) +{ + if (createTable.isNull()) + return; + + delete createTable->constraints[constrIdx]; + createTable->constraints[constrIdx] = constr; + constr->setParent(createTable); + modified = true; + + emit modifiyStateChanged(); + emit dataChanged(createIndex(constrIdx, 0), createIndex(constrIdx, columnCount()-1)); +} + +void TableConstraintsModel::constraintModified(int constrIdx) +{ + modified = true; + + emit modifiyStateChanged(); + emit dataChanged(createIndex(constrIdx, 0), createIndex(constrIdx, columnCount()-1)); +} + +void TableConstraintsModel::insertConstraint(int constrIdx, SqliteCreateTable::Constraint* constr) +{ + if (createTable.isNull()) + return; + + beginInsertRows(QModelIndex(), constrIdx, constrIdx); + createTable->constraints.insert(constrIdx, constr); + constr->setParent(createTable); + endInsertRows(); + + modified = true; + emit modifiyStateChanged(); +} + +void TableConstraintsModel::appendConstraint(SqliteCreateTable::Constraint* constr) +{ + if (createTable.isNull()) + return; + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + createTable->constraints.append(constr); + constr->setParent(createTable); + endInsertRows(); + + modified = true; + emit modifiyStateChanged(); +} + +void TableConstraintsModel::delConstraint(int constrIdx) +{ + if (createTable.isNull()) + return; + + beginRemoveRows(QModelIndex(), constrIdx, constrIdx); + delete createTable->constraints[constrIdx]; + createTable->constraints.removeAt(constrIdx); + endRemoveRows(); + + modified = true; + emit modifiyStateChanged(); +} + +void TableConstraintsModel::moveConstraintUp(int constrIdx) +{ + moveConstraintColumnTo(constrIdx, constrIdx-1); +} + +void TableConstraintsModel::moveConstraintDown(int constrIdx) +{ + moveConstraintColumnTo(constrIdx, constrIdx+1); +} + +void TableConstraintsModel::moveConstraintColumnTo(int constrIdx, int newIdx) +{ + if (createTable.isNull()) + return; + + if (newIdx == constrIdx) + return; + + if (newIdx == constrIdx+1) + { + // See TableStructureModel::moveColumnTo() for details above code below. + int tmpIdx = newIdx; + newIdx = constrIdx; + constrIdx = tmpIdx; + } + + beginMoveRows(QModelIndex(), constrIdx, constrIdx, QModelIndex(), newIdx); + if (newIdx >= createTable->constraints.size()) + { + SqliteCreateTable::Constraint* constr = createTable->constraints.takeAt(constrIdx); + createTable->constraints.append(constr); + } + else + createTable->constraints.move(constrIdx, newIdx); + + endMoveRows(); + + modified = true; + emit modifiyStateChanged(); + emit constraintOrderChanged(); +} + +TableConstraintsModel::Columns TableConstraintsModel::getColumn(int idx) const +{ + return static_cast(idx); +} + +QString TableConstraintsModel::getTypeLabel(SqliteCreateTable::Constraint::Type type) const +{ + switch (type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return "PRIMARY KEY"; + case SqliteCreateTable::Constraint::UNIQUE: + return "UNIQUE"; + case SqliteCreateTable::Constraint::CHECK: + return "CHECK"; + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return "FOREIGN KEY"; + case SqliteCreateTable::Constraint::NAME_ONLY: + return QString::null; + } + return QString::null; +} + +QIcon TableConstraintsModel::getTypeIcon(SqliteCreateTable::Constraint::Type type) const +{ + switch (type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return ICONS.CONSTRAINT_PRIMARY_KEY; + case SqliteCreateTable::Constraint::UNIQUE: + return ICONS.CONSTRAINT_UNIQUE; + case SqliteCreateTable::Constraint::CHECK: + return ICONS.CONSTRAINT_CHECK; + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return ICONS.CONSTRAINT_FOREIGN_KEY; + case SqliteCreateTable::Constraint::NAME_ONLY: + return QIcon(); + } + return QIcon(); +} + +QString TableConstraintsModel::getDetails(SqliteCreateTable::Constraint* constr) const +{ + switch (constr->type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return getPkDetails(constr); + case SqliteCreateTable::Constraint::UNIQUE: + return getUniqueDetails(constr); + case SqliteCreateTable::Constraint::CHECK: + return getCheckDetails(constr); + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return getFkDetails(constr); + case SqliteCreateTable::Constraint::NAME_ONLY: + return QString::null; + } + return QString::null; +} + +QString TableConstraintsModel::getPkDetails(SqliteCreateTable::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString TableConstraintsModel::getUniqueDetails(SqliteCreateTable::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString TableConstraintsModel::getCheckDetails(SqliteCreateTable::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString TableConstraintsModel::getFkDetails(SqliteCreateTable::Constraint* constr) const +{ + int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive); + return getConstrDetails(constr, idx + 1); +} + +QString TableConstraintsModel::getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const +{ + TokenList tokens = constr->tokens.mid(tokenOffset); + tokens.trimLeft(); + return tokens.detokenize(); +} + +void TableConstraintsModel::columnRenamed(SqliteCreateTable::Constraint* constr, const QString& oldColumn, const QString& newColumn) +{ + foreach (SqliteIndexedColumn* idxCol, constr->indexedColumns) + { + if (idxCol->name.compare(oldColumn, Qt::CaseInsensitive) == 0) + { + idxCol->name = newColumn; + modified = true; + } + } + + emit modifiyStateChanged(); +} + +bool TableConstraintsModel::handleColumnDeleted(SqliteCreateTable::Constraint* constr, const QString& column) +{ + switch (constr->type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + case SqliteCreateTable::Constraint::UNIQUE: + { + QMutableListIterator it(constr->indexedColumns); + SqliteIndexedColumn* idxCol = nullptr; + while (it.hasNext()) + { + idxCol = it.next(); + if (idxCol->name.compare(column, Qt::CaseInsensitive) == 0) + { + it.remove(); + delete idxCol; + modified = true; + } + } + + emit modifiyStateChanged(); + return constr->indexedColumns.count() > 0; + } + case SqliteCreateTable::Constraint::CHECK: + case SqliteCreateTable::Constraint::FOREIGN_KEY: + case SqliteCreateTable::Constraint::NAME_ONLY: + break; + } + return true; +} + +void TableConstraintsModel::columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn) +{ + if (newColumn->name == oldColumn) + return; + + int idx = 0; + foreach (SqliteCreateTable::Constraint* constr, createTable->constraints) + { + if (constr->doesAffectColumn(oldColumn)) + { + columnRenamed(constr, oldColumn, newColumn->name); + constr->rebuildTokens(); + emit dataChanged(createIndex(idx, 0), createIndex(idx, columnCount()-1)); + } + + idx++; + } +} + +void TableConstraintsModel::columnDeleted(const QString& column) +{ + QList toDelete; + int idx = 0; + foreach (SqliteCreateTable::Constraint* constr, createTable->constraints) + { + if (constr->doesAffectColumn(column)) + { + if (handleColumnDeleted(constr, column)) + { + constr->rebuildTokens(); + emit dataChanged(createIndex(idx, 0), createIndex(idx, columnCount()-1)); + } + else + toDelete << idx; + } + + idx++; + } + + foreach (int idx, toDelete) + delConstraint(idx); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h new file mode 100644 index 0000000..e3ee9e3 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h @@ -0,0 +1,72 @@ +#ifndef TABLECONSTRAINTSMODEL_H +#define TABLECONSTRAINTSMODEL_H + +#include "parser/ast/sqlitecreatetable.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class GUI_API_EXPORT TableConstraintsModel : public QAbstractTableModel +{ + Q_OBJECT + public: + explicit TableConstraintsModel(QObject *parent = 0); + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + Qt::DropActions supportedDropActions() const; + Qt::DropActions supportedDragActions() const; + QStringList mimeTypes() const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + Qt::ItemFlags flags(const QModelIndex& index) const; + bool isModified() const; + void setCreateTable(SqliteCreateTable* value); + SqliteCreateTable::Constraint* getConstraint(int constrIdx) const; + void replaceConstraint(int constrIdx, SqliteCreateTable::Constraint* constr); + void constraintModified(int constrIdx); + void insertConstraint(int constrIdx, SqliteCreateTable::Constraint* constr); + void appendConstraint(SqliteCreateTable::Constraint* constr); + void delConstraint(int constrIdx); + void moveConstraintUp(int constrIdx); + void moveConstraintDown(int constrIdx); + void moveConstraintColumnTo(int constrIdx, int newIdx); + + private: + enum class Columns + { + TYPE, + NAME, + DETAILS + }; + + Columns getColumn(int idx) const; + QString getTypeLabel(SqliteCreateTable::Constraint::Type type) const; + QIcon getTypeIcon(SqliteCreateTable::Constraint::Type type) const; + QString getDetails(SqliteCreateTable::Constraint* constr) const; + QString getPkDetails(SqliteCreateTable::Constraint* constr) const; + QString getUniqueDetails(SqliteCreateTable::Constraint* constr) const; + QString getCheckDetails(SqliteCreateTable::Constraint* constr) const; + QString getFkDetails(SqliteCreateTable::Constraint* constr) const; + QString getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const; + void columnRenamed(SqliteCreateTable::Constraint* constr, const QString& oldColumn, const QString& newColumn); + bool handleColumnDeleted(SqliteCreateTable::Constraint* constr, const QString& column); + + static const constexpr char* mimeType = "application/x-sqlitestudio-tablestructureconstraintmodel-row-index"; + + QPointer createTable; + bool modified = false; + + public slots: + void columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn); + void columnDeleted(const QString& column); + + signals: + void modifiyStateChanged(); + void constraintOrderChanged(); +}; + +#endif // TABLECONSTRAINTSMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp new file mode 100644 index 0000000..80c4567 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp @@ -0,0 +1,604 @@ +#include "tablestructuremodel.h" +#include "iconmanager.h" +#include "common/unused.h" +#include "uiconfig.h" +#include +#include +#include + +TableStructureModel::TableStructureModel(QObject *parent) : + QAbstractTableModel(parent) +{ +} + +int TableStructureModel::rowCount(const QModelIndex& parent) const +{ + UNUSED(parent); + if (createTable.isNull()) + return 0; + + return createTable->columns.size(); +} + +int TableStructureModel::columnCount(const QModelIndex& parent) const +{ + UNUSED(parent); + if (createTable.isNull()) + return 0; + + switch (createTable->dialect) + { + case Dialect::Sqlite3: + return 9; + case Dialect::Sqlite2: + return 7; + } + return 0; +} + +QVariant TableStructureModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (createTable.isNull()) + return QVariant(); + + if (!isValidColumnIdx(index.column())) + return QVariant(); + + int row = index.row(); + if (createTable->columns.size() <= row) + return QVariant(); + + switch (getHeaderColumn(index.column())) + { + case TableStructureModel::Columns::NAME: + { + if (role != Qt::DisplayRole) + break; + + return getColumnName(row); + } + case TableStructureModel::Columns::TYPE: + { + if (role != Qt::DisplayRole) + break; + + return getColumnType(row); + } + case TableStructureModel::Columns::PK: + { + if (role != Qt::DecorationRole) + break; + + return getColumnPk(row); + } + case TableStructureModel::Columns::FK: + { + if (role != Qt::DecorationRole) + break; + + return getColumnFk(row); + } + case TableStructureModel::Columns::UNIQUE: + { + if (role != Qt::DecorationRole) + break; + + return getColumnUnique(row); + } + case TableStructureModel::Columns::CHECK: + { + if (role != Qt::DecorationRole) + break; + + return getColumnCheck(row); + } + case TableStructureModel::Columns::NOTNULL: + { + if (role != Qt::DecorationRole) + break; + + return getColumnNotNull(row); + } + case TableStructureModel::Columns::COLLATE: + { + if (role != Qt::DecorationRole) + break; + + return getColumnCollate(row); + } + case TableStructureModel::Columns::DEFAULT: + { + if (role == Qt::FontRole) + return getColumnDefaultFont(row); + + if (role == Qt::ForegroundRole) + return getColumnDefaultColor(row); + + if (role == Qt::DisplayRole) + return getColumnDefaultValue(row); + + break; + } + } + return QVariant(); +} + +QVariant TableStructureModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QAbstractTableModel::headerData(section, orientation, role); + + if (orientation == Qt::Vertical) + return section + 1; + + // Now it's horizontal orientation with DisplayRole + return columnLabel(section); +} + +TableStructureModel::Columns TableStructureModel::getHeaderColumn(int colIdx) const +{ + if (!createTable.isNull() && createTable->dialect == Dialect::Sqlite2) + { + if (colIdx >= 3) + colIdx++; // skip FK + + if (colIdx >= 7) + colIdx++; // skip COLLATE + } + return static_cast(colIdx); +} + +bool TableStructureModel::isValidColumnIdx(int colIdx) const +{ + if (!createTable.isNull() && createTable->dialect == Dialect::Sqlite2) + return colIdx >= 0 && colIdx < 7; + + return colIdx >= 0 && colIdx < 9; +} + +SqliteCreateTable::Column* TableStructureModel::getColumn(int colIdx) const +{ + if (createTable.isNull()) + return nullptr; + + return createTable->columns[colIdx]; +} + +void TableStructureModel::replaceColumn(int colIdx, SqliteCreateTable::Column* column) +{ + if (createTable.isNull()) + return; + + SqliteCreateTable::Column* oldColumn = createTable->columns[colIdx]; + QString oldColumnName = oldColumn->name; + + delete oldColumn; + createTable->columns[colIdx] = column; + column->setParent(createTable); + modified = true; + + emit modifiyStateChanged(); + emit dataChanged(createIndex(colIdx, 0), createIndex(colIdx, columnCount()-1)); + emit columnModified(oldColumnName, column); +} + +void TableStructureModel::insertColumn(int colIdx, SqliteCreateTable::Column* column) +{ + if (createTable.isNull()) + return; + + beginInsertRows(QModelIndex(), colIdx, colIdx); + createTable->columns.insert(colIdx, column); + column->setParent(createTable); + endInsertRows(); + + modified = true; + emit modifiyStateChanged(); +} + +void TableStructureModel::appendColumn(SqliteCreateTable::Column* column) +{ + if (createTable.isNull()) + return; + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + createTable->columns.append(column); + column->setParent(createTable); + endInsertRows(); + + modified = true; + emit modifiyStateChanged(); +} + +void TableStructureModel::delColumn(int colIdx) +{ + if (createTable.isNull()) + return; + + QString name = createTable->columns[colIdx]->name; + + beginRemoveRows(QModelIndex(), colIdx, colIdx); + delete createTable->columns[colIdx]; + createTable->columns.removeAt(colIdx); + endRemoveRows(); + + modified = true; + emit modifiyStateChanged(); + emit columnDeleted(name); +} + +void TableStructureModel::moveColumnUp(int colIdx) +{ + moveColumnTo(colIdx, colIdx-1); +} + +void TableStructureModel::moveColumnDown(int colIdx) +{ + moveColumnTo(colIdx, colIdx+1); +} + +void TableStructureModel::moveColumnTo(int colIdx, int newIdx) +{ + if (createTable.isNull()) + return; + + if (newIdx == colIdx) + return; + + int totalCols = createTable->columns.size(); + if (colIdx + 1 == totalCols && newIdx == totalCols) // Moving last column out of range? Nothing to do. + return; + + if (newIdx == colIdx+1) + { + // From Qt docs: "you must ensure that the destinationChild is not within the range of sourceFirst and sourceLast + 1". + // So in this case - which is easy to handle - we will invert operation. We will move target index one level up, + // instead of moving source index down. + int tmpIdx = newIdx; + newIdx = colIdx; + colIdx = tmpIdx; + } + + beginMoveRows(QModelIndex(), colIdx, colIdx, QModelIndex(), newIdx); + if (newIdx >= totalCols) + { + SqliteCreateTable::Column* col = createTable->columns.takeAt(colIdx); + createTable->columns.append(col); + } + else + createTable->columns.move(colIdx, newIdx); + + endMoveRows(); + + modified = true; + emit modifiyStateChanged(); + emit columnsOrderChanged(); +} + +QModelIndex TableStructureModel::findColumn(const QString& columnName, Qt::CaseSensitivity cs) const +{ + int row = 0; + foreach (SqliteCreateTable::Column* col, createTable->columns) + { + if (col->name.compare(columnName, cs) == 0) + return createIndex(row, 0); + + row++; + } + return QModelIndex(); +} + +QString TableStructureModel::columnLabel(int column) const +{ + switch (getHeaderColumn(column)) + { + case Columns::NAME: + return tr("Name", "table structure columns"); + case Columns::TYPE: + return tr("Data type", "table structure columns"); + case Columns::PK: + return "P"; + case Columns::FK: + return "F"; + case Columns::UNIQUE: + return "U"; + case Columns::CHECK: + return "H"; + case Columns::NOTNULL: + return "N"; + case Columns::COLLATE: + return "C"; + case Columns::DEFAULT: + return tr("Default value", "table structure columns"); + } + return QString::null; +} + +QVariant TableStructureModel::getColumnName(int row) const +{ + return getColumn(row)->name; +} + +QVariant TableStructureModel::getColumnType(int row) const +{ + SqliteColumnType* type = getColumn(row)->type; + return type ? type->detokenize() : ""; +} + +QVariant TableStructureModel::getColumnPk(int row) const +{ + if (isColumnPk(getColumn(row))) + return ICONS.CONSTRAINT_PRIMARY_KEY; + + return QVariant(); +} + +QVariant TableStructureModel::getColumnFk(int row) const +{ + if (isColumnFk(getColumn(row))) + return ICONS.CONSTRAINT_FOREIGN_KEY; + + return QVariant(); +} + +QVariant TableStructureModel::getColumnUnique(int row) const +{ + if (isColumnUnique(getColumn(row))) + return ICONS.CONSTRAINT_UNIQUE; + + return QVariant(); +} + +QVariant TableStructureModel::getColumnCheck(int row) const +{ + if (isColumnCheck(getColumn(row))) + return ICONS.CONSTRAINT_CHECK; + + return QVariant(); +} + +QVariant TableStructureModel::getColumnNotNull(int row) const +{ + if (isColumnNotNull(getColumn(row))) + return ICONS.CONSTRAINT_NOT_NULL; + + return QVariant(); +} + +QVariant TableStructureModel::getColumnCollate(int row) const +{ + if (isColumnCollate(getColumn(row))) + return ICONS.CONSTRAINT_COLLATION; + + return QVariant(); +} + +QVariant TableStructureModel::getColumnDefaultValue(int row) const +{ + QVariant value = getColumnDefault(row); + if (value.isNull()) + return "NULL"; + + return value; +} + +QVariant TableStructureModel::getColumnDefaultFont(int row) const +{ + QVariant value = getColumnDefault(row); + if (value.isNull()) + { + QFont font; + font.setItalic(true); + return font; + } + return QVariant(); +} + +QVariant TableStructureModel::getColumnDefaultColor(int row) const +{ + QVariant value = getColumnDefault(row); + if (value.isNull()) + return QColor(CFG_UI.Colors.DataNullFg); + + return QVariant(); +} + +QVariant TableStructureModel::getColumnDefault(int row) const +{ + SqliteCreateTable::Column::Constraint* constr = getColumn(row)->getConstraint(SqliteCreateTable::Column::Constraint::DEFAULT); + if (!constr) + return QVariant(); + + if (!constr->id.isNull()) + return constr->id; + else if (!constr->literalValue.isNull()) + return constr->literalValue; + else if (!constr->ctime.isNull()) + return constr->ctime; + else if (constr->expr) + return constr->expr->detokenize(); + else + return QVariant(); +} + +bool TableStructureModel::isColumnPk(SqliteCreateTable::Column* column) const +{ + if (column->hasConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY)) + return true; + + QList constraints = createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY); + foreach (SqliteCreateTable::Constraint* constr, constraints) + if (constr->doesAffectColumn(column->name)) + return true; + + return false; +} + +bool TableStructureModel::isColumnFk(SqliteCreateTable::Column* column) const +{ + if (column->hasConstraint(SqliteCreateTable::Column::Constraint::FOREIGN_KEY)) + return true; + + QList constraints = createTable->getConstraints(SqliteCreateTable::Constraint::FOREIGN_KEY); + foreach (SqliteCreateTable::Constraint* constr, constraints) + if (constr->doesAffectColumn(column->name)) + return true; + + return false; +} + +bool TableStructureModel::isColumnUnique(SqliteCreateTable::Column* column) const +{ + if (column->hasConstraint(SqliteCreateTable::Column::Constraint::UNIQUE)) + return true; + + QList constraints = createTable->getConstraints(SqliteCreateTable::Constraint::UNIQUE); + foreach (SqliteCreateTable::Constraint* constr, constraints) + if (constr->doesAffectColumn(column->name)) + return true; + + return false; +} + +bool TableStructureModel::isColumnCheck(SqliteCreateTable::Column* column) const +{ + if (column->hasConstraint(SqliteCreateTable::Column::Constraint::CHECK)) + return true; + + QList constraints = createTable->getConstraints(SqliteCreateTable::Constraint::CHECK); + foreach (SqliteCreateTable::Constraint* constr, constraints) + if (constr->expr->getContextColumns(false).contains(column->name, Qt::CaseInsensitive)) + return true; + + return false; +} + +bool TableStructureModel::isColumnNotNull(SqliteCreateTable::Column* column) const +{ + if (column->hasConstraint(SqliteCreateTable::Column::Constraint::NOT_NULL)) + return true; + + return false; +} + +bool TableStructureModel::isColumnCollate(SqliteCreateTable::Column* column) const +{ + if (column->hasConstraint(SqliteCreateTable::Column::Constraint::COLLATE)) + return true; + + return false; +} + +void TableStructureModel::setCreateTable(SqliteCreateTable* value) +{ + beginResetModel(); + createTable = value; + endResetModel(); + + modified = false; + emit modifiyStateChanged(); +} + +bool TableStructureModel::isModified() const +{ + return modified; +} + +Qt::DropActions TableStructureModel::supportedDropActions() const +{ + return Qt::MoveAction; +} + +Qt::DropActions TableStructureModel::supportedDragActions() const +{ + return Qt::CopyAction|Qt::MoveAction; +} + + +QStringList TableStructureModel::mimeTypes() const +{ + return {mimeType}; +} + +QMimeData* TableStructureModel::mimeData(const QModelIndexList& indexes) const +{ + if (indexes.size() < 1) + return nullptr; + + QModelIndex idx = indexes.first(); + + QMimeData *data = new QMimeData(); + + QByteArray output; + QDataStream stream(&output, QIODevice::WriteOnly); + + stream << idx.row(); + data->setData(mimeType, output); + + return data; +} + + +bool TableStructureModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +{ + UNUSED(action); + UNUSED(row); + UNUSED(column); + UNUSED(parent); + + if (!data) + return false; + + if (!data->hasFormat(mimeType)) + return false; + + return true; +} + +bool TableStructureModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + UNUSED(column); + + if (action == Qt::IgnoreAction) + return true; + + if (!data) + return false; + + if (!data->hasFormat(mimeType)) + return false; + + if (action != Qt::MoveAction) + return false; + + if (row < 0) + { + if (!parent.isValid() && !createTable.isNull()) + row = createTable->columns.size(); + else + row = parent.row(); + } + + if (row < 0) + return false; + + QByteArray byteData = data->data(mimeType); + QDataStream stream(&byteData, QIODevice::ReadOnly); + int oldRow; + stream >> oldRow; + + moveColumnTo(oldRow, row); + return true; +} + +Qt::ItemFlags TableStructureModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags defFlags = QAbstractItemModel::flags(index); + if (!index.isValid()) + return defFlags|Qt::ItemIsDropEnabled; + + return defFlags|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h new file mode 100644 index 0000000..e1cfa4e --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h @@ -0,0 +1,89 @@ +#ifndef TABLESTRUCTUREMODEL_H +#define TABLESTRUCTUREMODEL_H + +#include "parser/ast/sqlitecreatetable.h" +#include "guiSQLiteStudio_global.h" +#include +#include + +class GUI_API_EXPORT TableStructureModel : public QAbstractTableModel +{ + Q_OBJECT + + public: + explicit TableStructureModel(QObject *parent = 0); + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + Qt::DropActions supportedDropActions() const; + Qt::DropActions supportedDragActions() const; + QStringList mimeTypes() const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + Qt::ItemFlags flags(const QModelIndex& index) const; + bool isModified() const; + void setCreateTable(SqliteCreateTable* value); + SqliteCreateTable::Column* getColumn(int colIdx) const; + void replaceColumn(int colIdx, SqliteCreateTable::Column* column); + void insertColumn(int colIdx, SqliteCreateTable::Column* column); + void appendColumn(SqliteCreateTable::Column* column); + void delColumn(int colIdx); + void moveColumnUp(int colIdx); + void moveColumnDown(int colIdx); + void moveColumnTo(int colIdx, int newIdx); + QModelIndex findColumn(const QString& columnName, Qt::CaseSensitivity cs = Qt::CaseSensitive) const; + + private: + enum class Columns + { + NAME, + TYPE, + PK, + FK, + UNIQUE, + CHECK, + NOTNULL, + COLLATE, + DEFAULT + }; + + Columns getHeaderColumn(int colIdx) const; + bool isValidColumnIdx(int colIdx) const; + bool doesColumnHasConstraint(SqliteCreateTable::Column* column, SqliteCreateTable::Column::Constraint::Type type); + QString columnLabel(int column) const; + QString columnLabelForSqlite2(int column) const; + QVariant getColumnName(int row) const; + QVariant getColumnType(int row) const; + QVariant getColumnPk(int row) const; + QVariant getColumnFk(int row) const; + QVariant getColumnUnique(int row) const; + QVariant getColumnCheck(int row) const; + QVariant getColumnNotNull(int row) const; + QVariant getColumnCollate(int row) const; + QVariant getColumnDefaultValue(int row) const; + QVariant getColumnDefaultFont(int row) const; + QVariant getColumnDefaultColor(int row) const; + QVariant getColumnDefault(int row) const; + bool isColumnPk(SqliteCreateTable::Column* column) const; + bool isColumnFk(SqliteCreateTable::Column* column) const; + bool isColumnUnique(SqliteCreateTable::Column* column) const; + bool isColumnCheck(SqliteCreateTable::Column* column) const; + bool isColumnNotNull(SqliteCreateTable::Column* column) const; + bool isColumnCollate(SqliteCreateTable::Column* column) const; + + static const constexpr char* mimeType = "application/x-sqlitestudio-tablestructuremodel-row-index"; + + QPointer createTable; + bool modified = false; + + signals: + void columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn); + void columnDeleted(const QString& column); + void modifiyStateChanged(); + void columnsOrderChanged(); +}; + +#endif // TABLESTRUCTUREMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp new file mode 100644 index 0000000..56accd0 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp @@ -0,0 +1,1508 @@ +#include "tablewindow.h" +#include "ui_tablewindow.h" +#include "services/dbmanager.h" +#include "services/notifymanager.h" +#include "sqlitestudio.h" +#include "common/unused.h" +#include "schemaresolver.h" +#include "iconmanager.h" +#include "common/intvalidator.h" +#include "common/extlineedit.h" +#include "datagrid/sqltablemodel.h" +#include "common/extaction.h" +#include "mainwindow.h" +#include "tablestructuremodel.h" +#include "tableconstraintsmodel.h" +#include "dialogs/columndialog.h" +#include "dialogs/constraintdialog.h" +#include "mdiarea.h" +#include "sqlitesyntaxhighlighter.h" +#include "dialogs/newconstraintdialog.h" +#include "db/chainexecutor.h" +#include "common/widgetcover.h" +#include "mdiwindow.h" +#include "dbtree/dbtree.h" +#include "constrainttabmodel.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "dialogs/messagelistdialog.h" +#include "services/codeformatter.h" +#include "uiconfig.h" +#include "dialogs/ddlpreviewdialog.h" +#include "services/config.h" +#include "services/importmanager.h" +#include "dbobjectdialogs.h" +#include "dialogs/exportdialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO extend QTableView for columns and constraints, so they show full-row-width drop indicator, +// instead of single column drop indicator. + +CFG_KEYS_DEFINE(TableWindow) + +TableWindow::TableWindow(QWidget* parent) : + MdiChild(parent), + ui(new Ui::TableWindow) +{ + init(); + applyInitialTab(); +} + +TableWindow::TableWindow(Db* db, QWidget* parent) : + MdiChild(parent), + db(db), + ui(new Ui::TableWindow) +{ + newTable(); + init(); + initDbAndTable(); + applyInitialTab(); +} + +TableWindow::TableWindow(const TableWindow& win) : + MdiChild(win.parentWidget()), + db(win.db), + database(win.database), + table(win.table), + ui(new Ui::TableWindow) +{ + init(); + initDbAndTable(); + applyInitialTab(); +} + +TableWindow::TableWindow(QWidget *parent, Db* db, const QString& database, const QString& table) : + MdiChild(parent), + db(db), + database(database), + table(table), + ui(new Ui::TableWindow) +{ + init(); + initDbAndTable(); + applyInitialTab(); +} + +TableWindow::~TableWindow() +{ + delete ui; + + if (tableModifier) + { + delete tableModifier; + tableModifier = nullptr; + } +} + +void TableWindow::staticInit() +{ + qRegisterMetaType("TableWindow"); +} + +void TableWindow::insertAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertAction(action, toolbar); +} + +void TableWindow::insertActionBefore(ExtActionPrototype* action, TableWindow::Action beforeAction, TableWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); +} + +void TableWindow::insertActionAfter(ExtActionPrototype* action, TableWindow::Action afterAction, TableWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); +} + +void TableWindow::removeAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar) +{ + ExtActionContainer::removeAction(action, toolbar); +} + +void TableWindow::newTable() +{ + existingTable = false; + table = ""; +} + +void TableWindow::init() +{ + ui->setupUi(this); + ui->structureSplitter->setStretchFactor(0, 2); + +#ifdef Q_OS_MACX + QStyle *fusion = QStyleFactory::create("Fusion"); + ui->structureToolBar->setStyle(fusion); + ui->structureTab->layout()->setSpacing(0); + ui->tableConstraintsToolbar->setStyle(fusion); + ui->constraintsWidget->layout()->setSpacing(0); + ui->indexToolBar->setStyle(fusion); + ui->indexesTab->layout()->setSpacing(0); + ui->triggerToolBar->setStyle(fusion); + ui->triggersTab->layout()->setSpacing(0); +#endif + + dataModel = new SqlTableModel(this); + ui->dataView->init(dataModel); + + initActions(); + + connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful())); + connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString))); + connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); + connect(this, SIGNAL(modifyStatusChanged()), this, SLOT(updateStructureCommitState())); + connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(modifyStatusChanged())); + connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameChanged())); + connect(ui->indexList, SIGNAL(itemSelectionChanged()), this, SLOT(updateIndexesState())); + connect(ui->triggerList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState())); + + structureExecutor = new ChainExecutor(this); + connect(structureExecutor, SIGNAL(success()), this, SLOT(changesSuccessfullyCommited())); + connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString))); + + setupCoverWidget(); + updateAfterInit(); +} + +void TableWindow::createActions() +{ + createAction(EXPORT, ICONS.TABLE_EXPORT, tr("Export table", "table window"), this, SLOT(exportTable()), this); + createAction(IMPORT, ICONS.TABLE_IMPORT, tr("Import data to table", "table window"), this, SLOT(importTable()), this); + createAction(POPULATE, ICONS.TABLE_POPULATE, tr("Populate table", "table window"), this, SLOT(populateTable()), this); + + createStructureActions(); + createDataGridActions(); + createDataFormActions(); + createIndexActions(); + createTriggerActions(); + + createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this); + createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this); +} + +void TableWindow::createStructureActions() +{ + createAction(REFRESH_STRUCTURE, ICONS.RELOAD, tr("Refresh structure", "table window"), this, SLOT(refreshStructure()), ui->structureToolBar); + ui->structureToolBar->addSeparator(); + createAction(COMMIT_STRUCTURE, ICONS.COMMIT, tr("Commit structure changes", "table window"), this, SLOT(commitStructure()), ui->structureToolBar); + createAction(ROLLBACK_STRUCTURE, ICONS.ROLLBACK, tr("Rollback structure changes", "table window"), this, SLOT(rollbackStructure()), ui->structureToolBar); + createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add column", "table window"), this, SLOT(addColumn()), ui->structureToolBar, ui->structureView); + createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit column", "table window"), this, SLOT(editColumn()), ui->structureToolBar, ui->structureView); + createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete column", "table window"), this, SLOT(delColumn()), ui->structureToolBar, ui->structureView); + createAction(MOVE_COLUMN_UP, ICONS.MOVE_UP, tr("Move column up", "table window"), this, SLOT(moveColumnUp()), ui->structureToolBar, ui->structureView); + createAction(MOVE_COLUMN_DOWN, ICONS.MOVE_DOWN, tr("Move column down", "table window"), this, SLOT(moveColumnDown()), ui->structureToolBar, ui->structureView); + ui->structureToolBar->addSeparator(); + ui->structureToolBar->addAction(actionMap[IMPORT]); + ui->structureToolBar->addAction(actionMap[EXPORT]); + ui->structureToolBar->addAction(actionMap[POPULATE]); + ui->structureToolBar->addSeparator(); + createAction(CREATE_SIMILAR, ICONS.TABLE_CREATE_SIMILAR, tr("Create similar table", "table window"), this, SLOT(createSimilarTable()), ui->structureToolBar); + + // Table constraints + createAction(ADD_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_ADD, tr("Add table constraint", "table window"), this, SLOT(addConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView); + createAction(EDIT_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_EDIT, tr("Edit table constraint", "table window"), this, SLOT(editConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView); + createAction(DEL_TABLE_CONSTRAINT, ICONS.TABLE_COLUMN_DELETE, tr("Delete table constraint", "table window"), this, SLOT(delConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView); + createAction(MOVE_CONSTRAINT_UP, ICONS.MOVE_UP, tr("Move table constraint up", "table window"), this, SLOT(moveConstraintUp()), ui->tableConstraintsToolbar, ui->tableConstraintsView); + createAction(MOVE_CONSTRAINT_DOWN, ICONS.MOVE_DOWN, tr("Move table constraint down", "table window"), this, SLOT(moveConstraintDown()), ui->tableConstraintsToolbar, ui->tableConstraintsView); + ui->tableConstraintsToolbar->addSeparator(); + createAction(ADD_TABLE_PK, ICONS.CONSTRAINT_PRIMARY_KEY_ADD, tr("Add table primary key", "table window"), this, SLOT(addPk()), ui->tableConstraintsToolbar, ui->tableConstraintsView); + createAction(ADD_TABLE_FK, ICONS.CONSTRAINT_FOREIGN_KEY_ADD, tr("Add table foreign key", "table window"), this, SLOT(addFk()), ui->tableConstraintsToolbar, ui->tableConstraintsView); + createAction(ADD_TABLE_UNIQUE, ICONS.CONSTRAINT_UNIQUE_ADD, tr("Add table unique constraint", "table window"), this, SLOT(addUnique()), ui->tableConstraintsToolbar, ui->tableConstraintsView); + createAction(ADD_TABLE_CHECK, ICONS.CONSTRAINT_CHECK_ADD, tr("Add table check constraint", "table window"), this, SLOT(addCheck()), ui->tableConstraintsToolbar, ui->tableConstraintsView); +} + +void TableWindow::createDataGridActions() +{ + QAction* before = ui->dataView->getAction(DataView::FILTER_VALUE); + ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[IMPORT]); + ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[EXPORT]); + ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[POPULATE]); + ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertSeparator(before); +} + +void TableWindow::createDataFormActions() +{ +} + +void TableWindow::createIndexActions() +{ + createAction(REFRESH_INDEXES, ICONS.RELOAD, tr("Refresh index list", "table window"), this, SLOT(updateIndexes()), ui->indexToolBar, ui->indexList); + ui->indexToolBar->addSeparator(); + createAction(ADD_INDEX, ICONS.INDEX_ADD, tr("Create index", "table window"), this, SLOT(addIndex()), ui->indexToolBar, ui->indexList); + createAction(EDIT_INDEX, ICONS.INDEX_EDIT, tr("Edit index", "table window"), this, SLOT(editIndex()), ui->indexToolBar, ui->indexList); + createAction(DEL_INDEX, ICONS.INDEX_DEL, tr("Delete index", "table window"), this, SLOT(delIndex()), ui->indexToolBar, ui->indexList); + connect(ui->indexList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editIndex())); +} + +void TableWindow::createTriggerActions() +{ + createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "table window"), this, SLOT(updateTriggers()), ui->triggerToolBar, ui->triggerList); + ui->triggerToolBar->addSeparator(); + createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create trigger", "table window"), this, SLOT(addTrigger()), ui->triggerToolBar, ui->triggerList); + createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit trigger", "table window"), this, SLOT(editTrigger()), ui->triggerToolBar, ui->triggerList); + createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete trigger", "table window"), this, SLOT(delTrigger()), ui->triggerToolBar, ui->triggerList); + connect(ui->triggerList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editTrigger())); +} + +void TableWindow::editColumn(const QModelIndex& idx) +{ + if (!idx.isValid()) + { + qWarning() << "Called TableWindow::editColumn() with invalid index."; + return; + } + + SqliteCreateTable::Column* column = structureModel->getColumn(idx.row()); + ColumnDialog columnDialog(db, this); + columnDialog.setColumn(column); + if (columnDialog.exec() != QDialog::Accepted) + return; + + SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn(); + structureModel->replaceColumn(idx.row(), modifiedColumn); + ui->structureView->resizeColumnToContents(0); +} + +void TableWindow::delColumn(const QModelIndex& idx) +{ + if (!idx.isValid()) + { + qWarning() << "Called TableWindow::delColumn() with invalid index."; + return; + } + + SqliteCreateTable::Column* column = structureModel->getColumn(idx.row()); + + QString msg = tr("Are you sure you want to delete column '%1'?", "table window").arg(column->name); + int btn = QMessageBox::question(this, tr("Delete column", "table window"), msg); + if (btn != QMessageBox::Yes) + return; + + structureModel->delColumn(idx.row()); + ui->structureView->resizeColumnToContents(0); +} + +void TableWindow::executeStructureChanges() +{ + QStringList sqls; + + createTable->rebuildTokens(); + if (!existingTable) + { + sqls << createTable->detokenize(); + } + else + { + if (tableModifier) + delete tableModifier; + + tableModifier = new TableModifier(db, database, table); + tableModifier->alterTable(createTable); + + if (tableModifier->hasMessages()) + { + MessageListDialog dialog(tr("Following problems will take place while modifying the table.\n" + "Would you like to proceed?", "table window")); + dialog.setWindowTitle(tr("Table modification", "table window")); + foreach (const QString& error, tableModifier->getErrors()) + dialog.addError(error); + + foreach (const QString& warn, tableModifier->getWarnings()) + dialog.addWarning(warn); + + if (dialog.exec() != QDialog::Accepted) + return; + } + + sqls = tableModifier->generateSqls(); + } + + if (!CFG_UI.General.DontShowDdlPreview.get()) + { + DdlPreviewDialog dialog(db, this); + dialog.setDdl(sqls); + if (dialog.exec() != QDialog::Accepted) + return; + } + + modifyingThisTable = true; + structureExecutor->setDb(db); + structureExecutor->setQueries(sqls); + widgetCover->show(); + structureExecutor->exec(); +} + +void TableWindow::updateAfterInit() +{ + updateStructureCommitState(); + updateStructureToolbarState(); + updateTableConstraintsToolbarState(); + updateNewTableState(); + updateIndexesState(); + updateTriggersState(); +} + +QModelIndex TableWindow::structureCurrentIndex() const +{ + return ui->structureView->selectionModel()->currentIndex(); +} + +void TableWindow::updateStructureToolbarState() +{ + QItemSelectionModel *selModel = ui->structureView->selectionModel(); + bool validIdx = false; + bool isFirst = false; + bool isLast = false; + if (selModel) + { + QModelIndex currIdx = selModel->currentIndex(); + if (currIdx.isValid()) + { + validIdx = true; + if (currIdx.row() == 0) + isFirst = true; + + if (currIdx.row() == (structureModel->rowCount() - 1)) + isLast = true; + } + } + + actionMap[EDIT_COLUMN]->setEnabled(validIdx); + actionMap[DEL_COLUMN]->setEnabled(validIdx); + actionMap[MOVE_COLUMN_UP]->setEnabled(validIdx && !isFirst); + actionMap[MOVE_COLUMN_DOWN]->setEnabled(validIdx && !isLast); +} + +void TableWindow::updateStructureCommitState() +{ + bool modified = isModified(); + actionMap[COMMIT_STRUCTURE]->setEnabled(modified); + actionMap[ROLLBACK_STRUCTURE]->setEnabled(modified && existingTable); +} + +void TableWindow::updateTableConstraintsToolbarState() +{ + QItemSelectionModel *selModel = ui->tableConstraintsView->selectionModel(); + bool anyColumn = structureModel && structureModel->rowCount() > 0; + bool validIdx = false; + bool isFirst = false; + bool isLast = false; + if (selModel) + { + QModelIndex currIdx = selModel->currentIndex(); + if (currIdx.isValid()) + { + validIdx = true; + if (currIdx.row() == 0) + isFirst = true; + + if (currIdx.row() == (structureConstraintsModel->rowCount() - 1)) + isLast = true; + } + } + + actionMap[EDIT_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx); + actionMap[DEL_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx); + actionMap[MOVE_CONSTRAINT_UP]->setEnabled(anyColumn && validIdx && !isFirst); + actionMap[MOVE_CONSTRAINT_DOWN]->setEnabled(anyColumn && validIdx && !isLast); +} + +void TableWindow::setupDefShortcuts() +{ + // Widget context + setShortcutContext({ + REFRESH_STRUCTURE, + REFRESH_INDEXES, + REFRESH_TRIGGERS, + ADD_COLUMN, + EDIT_COLUMN, + DEL_COLUMN, + ADD_TABLE_CONSTRAINT, + EDIT_TABLE_CONSTRAINT, + DEL_TABLE_CONSTRAINT, + ADD_INDEX, + EDIT_INDEX, + DEL_INDEX, + ADD_TRIGGER, + EDIT_TRIGGER, + DEL_TRIGGER, + }, + Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(TableWindow, Action); +} + +void TableWindow::executionSuccessful() +{ + modifyingThisTable = false; + dataLoaded = true; +} + +void TableWindow::executionFailed(const QString& errorText) +{ + modifyingThisTable = false; + notifyError(tr("Could not load data for table %1. Error details: %2").arg(table).arg(errorText)); +} + +void TableWindow::initDbAndTable() +{ + if (db->getVersion() == 2) + { + ui->withoutRowIdCheck->setVisible(false); + } + + if (existingTable) + { + dataModel->setDb(db); + dataModel->setDatabaseAndTable(database, table); + } + + ui->tableNameEdit->setText(table); // TODO no attached/temp db name support here + + if (structureModel) + { + delete structureModel; + structureModel = nullptr; + } + + if (structureConstraintsModel) + { + delete structureConstraintsModel; + structureConstraintsModel = nullptr; + } + + if (constraintTabModel) + { + delete constraintTabModel; + constraintTabModel = nullptr; + } + + structureModel = new TableStructureModel(this); + structureConstraintsModel = new TableConstraintsModel(this); + constraintTabModel = new ConstraintTabModel(this); + + // Columns model signals + connect(structureModel, SIGNAL(columnModified(QString,SqliteCreateTable::Column*)), + structureConstraintsModel, SLOT(columnModified(QString,SqliteCreateTable::Column*))); + connect(structureModel, SIGNAL(columnDeleted(QString)), + structureConstraintsModel, SLOT(columnDeleted(QString))); + connect(structureModel, SIGNAL(columnsOrderChanged()), this, SLOT(updateStructureToolbarState())); + + connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab())); + connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab())); + connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab())); + connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab())); + connect(structureModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged())); + + ui->structureView->setModel(structureModel); + ui->structureView->verticalHeader()->setDefaultSectionSize(ui->structureView->fontMetrics().height() + 8); + + // Constraints model signals + connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab())); + connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab())); + connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab())); + connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab())); + + connect(structureConstraintsModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged())); + connect(structureConstraintsModel, SIGNAL(constraintOrderChanged()), this, SLOT(updateTableConstraintsToolbarState())); + + ui->tableConstraintsView->setModel(structureConstraintsModel); + ui->tableConstraintsView->verticalHeader()->setDefaultSectionSize(ui->tableConstraintsView->fontMetrics().height() + 8); + + // Constraint tab model signals + connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel())); + connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel())); + connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel())); + connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel())); + connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel())); + connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel())); + connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel())); + connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel())); + + ui->constraintsView->setModel(constraintTabModel); + + connect(ui->withoutRowIdCheck, SIGNAL(clicked()), this, SLOT(withOutRowIdChanged())); + + ui->ddlEdit->setSqliteVersion(db->getVersion()); + parseDdl(); + updateIndexes(); + updateTriggers(); + + // (Re)connect to DB signals + connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfTableDeleted(QString,QString,DbObjectType))); + + // Selection model is recreated when setModel() is called on the view + connect(ui->structureView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(updateStructureToolbarState())); + connect(ui->tableConstraintsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(updateTableConstraintsToolbarState())); +} + +void TableWindow::setupCoverWidget() +{ + widgetCover = new WidgetCover(this); + widgetCover->initWithInterruptContainer(); + widgetCover->hide(); + connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt())); +} + +void TableWindow::parseDdl() +{ + if (existingTable) + { + SchemaResolver resolver(db); + SqliteQueryPtr parsedObject = resolver.getParsedObject(database, table, SchemaResolver::TABLE); + if (!parsedObject.dynamicCast()) + { + notifyError(tr("Could not process the %1 table correctly. Unable to open a table window.").arg(table)); + invalid = true; + return; + } + + createTable = parsedObject.dynamicCast(); + } + else + { + createTable = SqliteCreateTablePtr::create(); + createTable->table = table; + createTable->dialect = db->getDialect(); + } + originalCreateTable = SqliteCreateTablePtr::create(*createTable); + structureModel->setCreateTable(createTable.data()); + structureConstraintsModel->setCreateTable(createTable.data()); + constraintTabModel->setCreateTable(createTable.data()); + ui->withoutRowIdCheck->setChecked(!createTable->withOutRowId.isNull()); + ui->tableConstraintsView->resizeColumnsToContents(); + ui->structureView->resizeColumnsToContents(); + ui->constraintsView->resizeColumnsToContents(); + + updateStructureToolbarState(); + updateTableConstraintsToolbarState(); + updateDdlTab(); +} + +void TableWindow::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +QVariant TableWindow::saveSession() +{ + if (!db || DBLIST->isTemporary(db)) + return QVariant(); + + QHash sessionValue; + sessionValue["table"] = table; + sessionValue["db"] = db->getName(); + return sessionValue; +} + +bool TableWindow::restoreSession(const QVariant& sessionValue) +{ + QHash value = sessionValue.toHash(); + if (value.size() == 0) + { + notifyWarn("Could not restore window, because no database or table was stored in session for this window."); + return false; + } + + if (!value.contains("db") || !value.contains("table")) + { + notifyWarn("Could not restore window, because no database or table was stored in session for this window."); + return false; + } + + db = DBLIST->getByName(value["db"].toString()); + if (!db || !db->isValid() || (!db->isOpen() && !db->open())) + { + notifyWarn(tr("Could not restore window, because database %1 could not be resolved.").arg(value["db"].toString())); + return false; + } + + table = value["table"].toString(); + database = value["database"].toString(); + SchemaResolver resolver(db); + if (!resolver.getTables(database).contains(table, Qt::CaseInsensitive)) + { + notifyWarn(tr("Could not restore window, because the table %1 doesn't exist in the database %2.").arg(table).arg(db->getName())); + return false; + } + + initDbAndTable(); + applyInitialTab(); + return true; +} + +Icon* TableWindow::getIconNameForMdiWindow() +{ + return ICONS.TABLE; +} + +QString TableWindow::getTitleForMdiWindow() +{ + QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")")); + if (existingTable) + return table + dbSuffix; + + QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles(); + if (existingNames.contains(windowTitle())) + return windowTitle(); + + // Generate new name + QString title = tr("New table %1").arg(newTableWindowNum++); + while (existingNames.contains(title)) + title = tr("New table %1").arg(newTableWindowNum++); + + title += dbSuffix; + return title; +} + +Db* TableWindow::getDb() const +{ + return db; +} + +QString TableWindow::getTable() const +{ + return table; +} + +void TableWindow::dbClosedFinalCleanup() +{ + db = nullptr; + dataModel->setDb(nullptr); + structureExecutor->setDb(nullptr); +} + +void TableWindow::checkIfTableDeleted(const QString& database, const QString& object, DbObjectType type) +{ + UNUSED(database); + + // TODO uncomment below when dbnames are supported +// if (this->database != database) +// return; + + switch (type) + { + case DbObjectType::TABLE: + break; + case DbObjectType::INDEX: + checkIfIndexDeleted(object); + return; + case DbObjectType::TRIGGER: + checkIfTriggerDeleted(object); + return; + case DbObjectType::VIEW: + return; + } + + if (modifyingThisTable) + return; + + if (object.compare(table, Qt::CaseInsensitive) == 0) + { + dbClosedFinalCleanup(); + getMdiWindow()->close(); + } +} + +void TableWindow::checkIfIndexDeleted(const QString& object) +{ + for (int i = 0, total = ui->indexList->rowCount(); i < total; ++i) + { + if (ui->indexList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0) + { + ui->indexList->removeRow(i); + return; + } + } +} + +void TableWindow::checkIfTriggerDeleted(const QString& object) +{ + for (int i = 0, total = ui->triggerList->rowCount(); i < total; ++i) + { + if (ui->triggerList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0) + { + ui->triggerList->removeRow(i); + return; + } + } +} + +void TableWindow::refreshStructure() +{ + parseDdl(); + updateIndexes(); + updateTriggers(); +} + +void TableWindow::commitStructure(bool skipWarning) +{ + if (!isModified()) + { + qWarning() << "Called TableWindow::commitStructure(), but isModified() returned false."; + updateStructureCommitState(); + return; + } + + if (!validate(skipWarning)) + return; + + executeStructureChanges(); +} + +void TableWindow::changesSuccessfullyCommited() +{ + QStringList sqls = structureExecutor->getQueries(); + CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath()); + + widgetCover->hide(); + + originalCreateTable = createTable; + structureModel->setCreateTable(createTable.data()); + structureConstraintsModel->setCreateTable(createTable.data()); + dataLoaded = false; + + QString oldTable = table; + database = createTable->database; + table = createTable->table; + existingTable = true; + initDbAndTable(); + updateStructureCommitState(); + updateNewTableState(); + updateWindowTitle(); + + DBTREE->refreshSchema(db); + + if (tableModifier) + { + QList modifiedObjects = { + tableModifier->getModifiedTables(), + tableModifier->getModifiedIndexes(), + tableModifier->getModifiedTriggers(), + tableModifier->getModifiedViews() + }; + NotifyManager* notifyManager = NotifyManager::getInstance(); + foreach (const QStringList& objList, modifiedObjects) + { + foreach (const QString& obj, objList) + { + if (obj.compare(oldTable, Qt::CaseInsensitive) == 0) + continue; + + notifyManager->modified(db, database, obj); + } + } + } +} + +void TableWindow::changesFailedToCommit(int errorCode, const QString& errorText) +{ + qDebug() << "TableWindow::changesFailedToCommit:" << errorCode << errorText; + + widgetCover->hide(); + notifyError(tr("Could not commit table structure. Error message: %1", "table window").arg(errorText)); +} + +void TableWindow::rollbackStructure() +{ + createTable = SqliteCreateTablePtr::create(*originalCreateTable.data()); + structureModel->setCreateTable(createTable.data()); + structureConstraintsModel->setCreateTable(createTable.data()); + constraintTabModel->setCreateTable(createTable.data()); + ui->tableNameEdit->setText(createTable->table); + + updateStructureCommitState(); + updateStructureToolbarState(); + updateTableConstraintsToolbarState(); +} + +void TableWindow::addColumn() +{ + SqliteCreateTable::Column column; + column.setParent(createTable.data()); + + ColumnDialog columnDialog(db, this); + columnDialog.setColumn(&column); + if (columnDialog.exec() != QDialog::Accepted) + return; + + SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn(); + structureModel->appendColumn(modifiedColumn); + ui->structureView->resizeColumnToContents(0); + + ui->structureView->setCurrentIndex(structureModel->index(structureModel->rowCount()-1, 0)); +} + +void TableWindow::editColumn() +{ + editColumn(structureCurrentIndex()); +} + +void TableWindow::delColumn() +{ + QModelIndex idx = structureCurrentIndex(); + delColumn(idx); +} + +void TableWindow::moveColumnUp() +{ + QModelIndex idx = structureCurrentIndex(); + if (!idx.isValid()) + { + qWarning() << "Called TableWindow::moveColumnUp() with invalid index."; + return; + } + + structureModel->moveColumnUp(idx.row()); +} + +void TableWindow::moveColumnDown() +{ + QModelIndex idx = structureCurrentIndex(); + if (!idx.isValid()) + { + qWarning() << "Called TableWindow::moveColumnDown() with invalid index."; + return; + } + + structureModel->moveColumnDown(idx.row()); +} + + +void TableWindow::addConstraint(ConstraintDialog::Constraint mode) +{ + NewConstraintDialog dialog(mode, createTable.data(), db, this); + if (dialog.exec() != QDialog::Accepted) + return; + + SqliteStatement* constrStmt = dialog.getConstraint(); + SqliteCreateTable::Constraint* tableConstr = dynamic_cast(constrStmt); + if (!tableConstr) + { + qCritical() << "Constraint returned from ConstraintDialog was not of table type, while we're trying to add table constraint."; + return; + } + + structureConstraintsModel->appendConstraint(tableConstr); + ui->tableConstraintsView->resizeColumnToContents(0); + ui->tableConstraintsView->resizeColumnToContents(1); +} + +bool TableWindow::validate(bool skipWarning) +{ + if (!existingTable && !skipWarning && ui->tableNameEdit->text().isEmpty()) + { + int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the table is allowed in SQLite, but it is not recommended.\n" + "Are you sure you want to create a table with blank name?"), QMessageBox::Yes, QMessageBox::No); + + if (res != QMessageBox::Yes) + return false; + } + + if (structureModel->rowCount() == 0) + { + notifyError(tr("Cannot create a table without at least one column.")); + return false; + } + + if (ui->withoutRowIdCheck->isChecked()) + { + bool hasPk = false; + bool isPkAutoIncr = false; + + if (createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY).size() > 0) + hasPk = true; + + SqliteCreateTable::Column::Constraint* colConstraint = nullptr; + foreach (SqliteCreateTable::Column* column, createTable->columns) + { + colConstraint = column->getConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY); + if (colConstraint) + { + hasPk = true; + if (colConstraint->autoincrKw) + isPkAutoIncr = true; + } + } + + if (!hasPk) + { + notifyError(tr("Cannot create table without ROWID, if it has no PRIMARY KEY defined." + " Either uncheck the WITHOUT ROWID, or define a PRIMARY KEY.")); + return false; + } + + if (isPkAutoIncr) + { + notifyError(tr("Cannot use AUTOINCREMENT for PRIMARY KEY when WITHOUT ROWID clause is used." + " Either uncheck the WITHOUT ROWID, or the AUTOINCREMENT in a PRIMARY KEY.")); + return false; + } + } + + return true; +} + +bool TableWindow::isModified() const +{ + return (structureModel && structureModel->isModified()) || + (structureConstraintsModel && structureConstraintsModel->isModified()) || + (originalCreateTable && + (originalCreateTable->table != ui->tableNameEdit->text() || + originalCreateTable->withOutRowId != createTable->withOutRowId) + ) || + !existingTable; +} + +TokenList TableWindow::indexColumnTokens(SqliteCreateIndexPtr index) +{ + if (index->indexedColumns.size() == 0) + return TokenList(); + + SqliteIndexedColumn* firstCol = index->indexedColumns.first(); + SqliteIndexedColumn* lastCol = index->indexedColumns.last(); + if (firstCol->tokens.size() == 0) + return TokenList(); + + if (lastCol->tokens.size() == 0) + return TokenList(); + + int firstIdx = index->tokens.indexOf(firstCol->tokens.first()); + int lastIdx = index->tokens.indexOf(lastCol->tokens.last()); + + return index->tokens.mid(firstIdx, lastIdx-firstIdx+1); +} + +QString TableWindow::getCurrentIndex() const +{ + int row = ui->indexList->currentRow(); + QTableWidgetItem* item = ui->indexList->item(row, 0); + if (!item) + return QString::null; + + return item->text(); +} + +QString TableWindow::getCurrentTrigger() const +{ + int row = ui->triggerList->currentRow(); + QTableWidgetItem* item = ui->triggerList->item(row, 0); + if (!item) + return QString::null; + + return item->text(); +} + +void TableWindow::applyInitialTab() +{ + if (existingTable && !table.isNull() && CFG_UI.General.OpenTablesOnData.get()) + ui->tabWidget->setCurrentIndex(1); + else + ui->tabWidget->setCurrentIndex(0); +} + +void TableWindow::updateDdlTab() +{ + CodeFormatter* formatter = SQLITESTUDIO->getCodeFormatter(); + createTable->rebuildTokens(); + ui->ddlEdit->setPlainText(formatter->format("sql", createTable->detokenize(), db)); +} + +void TableWindow::updateNewTableState() +{ + for (int i = 1; i < 5; i++) + ui->tabWidget->setTabEnabled(i, existingTable); + + actionMap[EXPORT]->setEnabled(existingTable); + actionMap[IMPORT]->setEnabled(existingTable); + actionMap[POPULATE]->setEnabled(existingTable); + actionMap[CREATE_SIMILAR]->setEnabled(existingTable); + actionMap[REFRESH_STRUCTURE]->setEnabled(existingTable); +} + +void TableWindow::addConstraint() +{ + addConstraint(ConstraintDialog::UNKNOWN); +} + +void TableWindow::editConstraint() +{ + QModelIndex idx = ui->tableConstraintsView->currentIndex(); + editConstraint(idx); +} + +void TableWindow::delConstraint() +{ + QModelIndex idx = ui->tableConstraintsView->currentIndex(); + delConstraint(idx); +} + +void TableWindow::editConstraint(const QModelIndex& idx) +{ + if (!idx.isValid()) + return; + + SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row()); + ConstraintDialog dialog(ConstraintDialog::EDIT, constr, createTable.data(), db, this); + if (dialog.exec() != QDialog::Accepted) + return; + + structureConstraintsModel->constraintModified(idx.row()); + ui->tableConstraintsView->resizeColumnToContents(0); + ui->tableConstraintsView->resizeColumnToContents(1); +} + +void TableWindow::delConstraint(const QModelIndex& idx) +{ + if (!idx.isValid()) + return; + + SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row()); + + QString arg = constr->name.isNull() ? constr->typeString() : constr->name; + QString msg = tr("Are you sure you want to delete table constraint '%1'?", "table window").arg(arg); + int btn = QMessageBox::question(this, tr("Delete constraint", "table window"), msg); + if (btn != QMessageBox::Yes) + return; + + structureConstraintsModel->delConstraint(idx.row()); + ui->structureView->resizeColumnToContents(0); +} + +void TableWindow::moveConstraintUp() +{ + QModelIndex idx = ui->tableConstraintsView->currentIndex(); + if (!idx.isValid()) + return; + + structureConstraintsModel->moveConstraintUp(idx.row()); + updateTableConstraintsToolbarState(); + updateStructureCommitState(); +} + +void TableWindow::moveConstraintDown() +{ + QModelIndex idx = ui->tableConstraintsView->currentIndex(); + if (!idx.isValid()) + return; + + structureConstraintsModel->moveConstraintDown(idx.row()); + updateTableConstraintsToolbarState(); + updateStructureCommitState(); +} + +void TableWindow::addPk() +{ + addConstraint(ConstraintDialog::PK); +} + +void TableWindow::addFk() +{ + addConstraint(ConstraintDialog::FK); +} + +void TableWindow::addUnique() +{ + addConstraint(ConstraintDialog::UNIQUE); +} + +void TableWindow::addCheck() +{ + addConstraint(ConstraintDialog::CHECK); +} + +void TableWindow::exportTable() +{ + if (!ExportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot export, because no export plugin is loaded.")); + return; + } + + ExportDialog dialog(this); + dialog.setTableMode(db, table); + dialog.exec(); +} + +void TableWindow::importTable() +{ + if (!ImportManager::isAnyPluginAvailable()) + { + notifyError(tr("Cannot import, because no import plugin is loaded.")); + return; + } + + ImportDialog dialog(this); + dialog.setDbAndTable(db, table); + if (dialog.exec() == QDialog::Accepted && dataLoaded) + ui->dataView->refreshData(); +} + +void TableWindow::populateTable() +{ + PopulateDialog dialog(this); + dialog.setDbAndTable(db, table); + if (dialog.exec() == QDialog::Accepted && dataLoaded) + ui->dataView->refreshData(); +} + +void TableWindow::createSimilarTable() +{ + DbObjectDialogs dialog(db); + dialog.addTableSimilarTo(QString(), table); +} + +void TableWindow::tabChanged(int newTab) +{ + switch (newTab) + { + case 1: + { + if (isModified()) + { + int res = QMessageBox::question(this, tr("Uncommited changes"), + tr("There are uncommited structure modifications. You cannot browse or edit data until you have " + "table structure settled.\n" + "Do you want to commit the structure, or do you want to go back to the structure tab?"), + tr("Go back to structure tab"), tr("Commit modifications and browse data.")); + + ui->tabWidget->setCurrentIndex(0); + if (res == 1) + commitStructure(true); + + break; + } + + if (!dataLoaded) + ui->dataView->refreshData(); + + break; + } + } +} + +void TableWindow::on_structureView_doubleClicked(const QModelIndex &index) +{ + editColumn(index); +} + +void TableWindow::on_tableConstraintsView_doubleClicked(const QModelIndex &index) +{ + editConstraint(index); +} + +void TableWindow::nameChanged() +{ + if (!createTable) + return; + + createTable->table = ui->tableNameEdit->text(); + updateDdlTab(); +} + +void TableWindow::withOutRowIdChanged() +{ + if (!createTable) + return; + + createTable->withOutRowId = ui->withoutRowIdCheck->isChecked() ? QStringLiteral("ROWID") : QString::null; + updateDdlTab(); + emit modifyStatusChanged(); +} + +void TableWindow::addIndex() +{ + DbObjectDialogs dialogs(db, this); + dialogs.addIndex(table); + updateIndexes(); +} + +void TableWindow::editIndex() +{ + QString index = getCurrentIndex(); + if (index.isNull()) + return; + + DbObjectDialogs dialogs(db, this); + dialogs.editIndex(index); + updateIndexes(); +} + +void TableWindow::delIndex() +{ + QString index = getCurrentIndex(); + if (index.isNull()) + return; + + DbObjectDialogs dialogs(db, this); + dialogs.dropObject(index); + updateIndexes(); +} + +void TableWindow::addTrigger() +{ + DbObjectDialogs dialogs(db, this); + dialogs.addTriggerOnTable(table); + updateTriggers(); +} + +void TableWindow::editTrigger() +{ + QString trigger = getCurrentTrigger(); + if (trigger.isNull()) + return; + + DbObjectDialogs dialogs(db, this); + dialogs.editTrigger(trigger); + updateTriggers(); +} + +void TableWindow::delTrigger() +{ + QString trigger = getCurrentTrigger(); + if (trigger.isNull()) + return; + + DbObjectDialogs dialogs(db, this); + dialogs.dropObject(trigger); + updateTriggers(); +} + +void TableWindow::updateIndexesState() +{ + bool editDel = ui->indexList->currentItem() != nullptr; + actionMap[REFRESH_INDEXES]->setEnabled(existingTable); + actionMap[ADD_INDEX]->setEnabled(existingTable); + actionMap[EDIT_INDEX]->setEnabled(editDel); + actionMap[DEL_INDEX]->setEnabled(editDel); +} + +void TableWindow::updateTriggersState() +{ + bool editDel = ui->triggerList->currentItem() != nullptr; + actionMap[REFRESH_TRIGGERS]->setEnabled(existingTable); + actionMap[ADD_TRIGGER]->setEnabled(existingTable); + actionMap[EDIT_TRIGGER]->setEnabled(editDel); + actionMap[DEL_TRIGGER]->setEnabled(editDel); +} + +void TableWindow::nextTab() +{ + int idx = ui->tabWidget->currentIndex(); + idx++; + ui->tabWidget->setCurrentIndex(idx); +} + +void TableWindow::prevTab() +{ + int idx = ui->tabWidget->currentIndex(); + idx--; + ui->tabWidget->setCurrentIndex(idx); +} + +void TableWindow::updateIndexes() +{ + ui->indexList->clear(); + + if (!db || !db->isValid()) + return; + + SchemaResolver resolver(db); + resolver.setIgnoreSystemObjects(true); + QList indexes = resolver.getParsedIndexesForTable(database, table); + + ui->indexList->setColumnCount(4); + ui->indexList->setRowCount(indexes.size()); + ui->indexList->setHorizontalHeaderLabels({ + tr("Name", "table window indexes"), + tr("Unique", "table window indexes"), + tr("Columns", "table window indexes"), + tr("Partial index condition", "table window indexes"), + }); + + Dialect dialect= db->getDialect(); + if (dialect == Dialect::Sqlite2) + ui->indexList->setColumnCount(3); + + ui->indexList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + + QTableWidgetItem* item = nullptr; + int row = 0; + foreach (SqliteCreateIndexPtr index, indexes) + { + item = new QTableWidgetItem(index->index); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->indexList->setItem(row, 0, item); + + // TODO a delegate to make the checkbox in the center, or use setCellWidget() + item = new QTableWidgetItem(); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + item->setCheckState(index->uniqueKw ? Qt::Checked : Qt::Unchecked); + ui->indexList->setItem(row, 1, item); + + item = new QTableWidgetItem(indexColumnTokens(index).detokenize()); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->indexList->setItem(row, 2, item); + + if (dialect == Dialect::Sqlite3) + { + item = new QTableWidgetItem(index->where ? index->where->detokenize() : ""); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->indexList->setItem(row, 3, item); + } + + row++; + } + + ui->indexList->resizeColumnsToContents(); + updateIndexesState(); +} + +void TableWindow::updateTriggers() +{ + if (!db || !db->isValid()) + return; + + SchemaResolver resolver(db); + QList triggers = resolver.getParsedTriggersForTable(database, table); + + ui->triggerList->setColumnCount(4); + ui->triggerList->setRowCount(triggers.size()); + ui->triggerList->horizontalHeader()->setMaximumSectionSize(200); + ui->triggerList->setHorizontalHeaderLabels({ + tr("Name", "table window triggers"), + tr("Event", "table window triggers"), + tr("Condition", "table window triggers"), + tr("Details", "table window triggers") + }); + + QTableWidgetItem* item = nullptr; + QString timeAndEvent; + int row = 0; + foreach (SqliteCreateTriggerPtr trig, triggers) + { + item = new QTableWidgetItem(trig->trigger); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->triggerList->setItem(row, 0, item); + + timeAndEvent = trig->tokensMap["trigger_time"].detokenize() + trig->tokensMap["trigger_event"].detokenize(); + item = new QTableWidgetItem(timeAndEvent); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->triggerList->setItem(row, 1, item); + + item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : ""); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->triggerList->setItem(row, 2, item); + + item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed()); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->triggerList->setItem(row, 3, item); + + row++; + } + + ui->triggerList->resizeColumnsToContents(); + updateTriggersState(); +} + +void TableWindow::editColumn(const QString& columnName) +{ + QModelIndex colIdx = structureModel->findColumn(columnName); + if (!colIdx.isValid()) + return; + + editColumn(colIdx); +} + +void TableWindow::delColumn(const QString& columnName) +{ + QModelIndex colIdx = structureModel->findColumn(columnName); + if (!colIdx.isValid()) + return; + + delColumn(colIdx); +} + +bool TableWindow::restoreSessionNextTime() +{ + return existingTable && db && !DBLIST->isTemporary(db); +} + +QToolBar* TableWindow::getToolBar(int toolbar) const +{ + switch (static_cast(toolbar)) + { + case TOOLBAR_STRUCTURE: + return ui->structureToolBar; + case TOOLBAR_INDEXES: + return ui->indexToolBar; + case TOOLBAR_TRIGGERS: + return ui->triggerToolBar; + } + return nullptr; +} + +bool TableWindow::handleInitialFocus() +{ + if (!existingTable) + { + ui->tableNameEdit->setFocus(); + return true; + } + return false; +} + +bool TableWindow::isUncommited() const +{ + return ui->dataView->isUncommited() || isModified(); +} + +QString TableWindow::getQuitUncommitedConfirmMessage() const +{ + QString title = getMdiWindow()->windowTitle(); + if (ui->dataView->isUncommited() && isModified()) + return tr("Table window \"%1\" has uncommited structure modifications and data.").arg(title); + else if (ui->dataView->isUncommited()) + return tr("Table window \"%1\" has uncommited data.").arg(title); + else if (isModified()) + return tr("Table window \"%1\" has uncommited structure modifications.").arg(title); + else + { + qCritical() << "Unhandled message case in TableWindow::getQuitUncommitedConfirmMessage()."; + return QString(); + } +} + +void TableWindow::useCurrentTableAsBaseForNew() +{ + newTable(); + ui->tableNameEdit->clear(); + updateWindowTitle(); + ui->tableNameEdit->setFocus(); + updateAfterInit(); +} + +Db* TableWindow::getAssociatedDb() const +{ + return db; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h new file mode 100644 index 0000000..b0ee1e3 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h @@ -0,0 +1,241 @@ +#ifndef TABLEWINDOW_H +#define TABLEWINDOW_H + +#include "db/db.h" +#include "mdichild.h" +#include "common/extactioncontainer.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreateindex.h" +#include "dialogs/constraintdialog.h" +#include "db/chainexecutor.h" +#include "guiSQLiteStudio_global.h" +#include + +class TableModifier; +class SqlTableModel; +class ExtLineEdit; +class IntValidator; +class QLabel; +class TableStructureModel; +class TableConstraintsModel; +class QProgressBar; +class WidgetCover; +class SqliteSyntaxHighlighter; +class ConstraintTabModel; + +namespace Ui { + class TableWindow; +} + +CFG_KEY_LIST(TableWindow, QObject::tr("Table window"), + CFG_KEY_ENTRY(REFRESH_STRUCTURE, Qt::Key_F5, QObject::tr("Refresh table structure")) + CFG_KEY_ENTRY(ADD_COLUMN, Qt::Key_Insert, QObject::tr("Add new column")) + CFG_KEY_ENTRY(EDIT_COLUMN, Qt::Key_Return, QObject::tr("Edit selected column")) + CFG_KEY_ENTRY(DEL_COLUMN, Qt::Key_Delete, QObject::tr("Delete selected column")) + CFG_KEY_ENTRY(EXPORT, Qt::CTRL + Qt::Key_E, QObject::tr("Export table data")) + CFG_KEY_ENTRY(IMPORT, Qt::CTRL + Qt::Key_I, QObject::tr("Import data to the table")) + CFG_KEY_ENTRY(ADD_TABLE_CONSTRAINT, Qt::Key_Insert, QObject::tr("Add new table constraint")) + CFG_KEY_ENTRY(EDIT_TABLE_CONSTRAINT, Qt::Key_Return, QObject::tr("Edit selected table constraint")) + CFG_KEY_ENTRY(DEL_TABLE_CONSTRAINT, Qt::Key_Delete, QObject::tr("Delete selected table constraint")) + CFG_KEY_ENTRY(REFRESH_INDEXES, Qt::Key_F5, QObject::tr("Refresh table index list")) + CFG_KEY_ENTRY(ADD_INDEX, Qt::Key_Insert, QObject::tr("Add new index")) + CFG_KEY_ENTRY(EDIT_INDEX, Qt::Key_Return, QObject::tr("Edit selected index")) + CFG_KEY_ENTRY(DEL_INDEX, Qt::Key_Delete, QObject::tr("Delete selected index")) + CFG_KEY_ENTRY(REFRESH_TRIGGERS, Qt::Key_F5, QObject::tr("Refresh table trigger list")) + CFG_KEY_ENTRY(ADD_TRIGGER, Qt::Key_Insert, QObject::tr("Add new trigger")) + CFG_KEY_ENTRY(EDIT_TRIGGER, Qt::Key_Return, QObject::tr("Edit selected trigger")) + CFG_KEY_ENTRY(DEL_TRIGGER, Qt::Key_Delete, QObject::tr("Delete selected trigger")) + CFG_KEY_ENTRY(NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next tab")) + CFG_KEY_ENTRY(PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous tab")) +) + +class GUI_API_EXPORT TableWindow : public MdiChild +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + // Structure tab + REFRESH_STRUCTURE, + COMMIT_STRUCTURE, + ROLLBACK_STRUCTURE, + ADD_COLUMN, + EDIT_COLUMN, + DEL_COLUMN, + MOVE_COLUMN_UP, + MOVE_COLUMN_DOWN, + ADD_TABLE_CONSTRAINT, + EDIT_TABLE_CONSTRAINT, + DEL_TABLE_CONSTRAINT, + ADD_TABLE_PK, + ADD_TABLE_FK, + ADD_TABLE_UNIQUE, + ADD_TABLE_CHECK, + MOVE_CONSTRAINT_UP, + MOVE_CONSTRAINT_DOWN, + EXPORT, + IMPORT, + POPULATE, + CREATE_SIMILAR, + // Indexes tab + REFRESH_INDEXES, + ADD_INDEX, + EDIT_INDEX, + DEL_INDEX, + // Triggers tab + REFRESH_TRIGGERS, + ADD_TRIGGER, + EDIT_TRIGGER, + DEL_TRIGGER, + // All tabs + NEXT_TAB, + PREV_TAB + }; + + enum ToolBar + { + TOOLBAR_STRUCTURE, + TOOLBAR_INDEXES, + TOOLBAR_TRIGGERS + }; + + explicit TableWindow(QWidget *parent = 0); + TableWindow(Db* db, QWidget *parent = 0); + TableWindow(const TableWindow& win); + TableWindow(QWidget *parent, Db* db, const QString& database, const QString& table); + ~TableWindow(); + + static void staticInit(); + static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_STRUCTURE); + static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_STRUCTURE); + static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_STRUCTURE); + static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_STRUCTURE); + + QString getTable() const; + Db* getDb() const; + bool handleInitialFocus(); + bool isUncommited() const; + QString getQuitUncommitedConfirmMessage() const; + void useCurrentTableAsBaseForNew(); + Db* getAssociatedDb() const; + + protected: + void changeEvent(QEvent *e); + QVariant saveSession(); + bool restoreSession(const QVariant& sessionValue); + Icon* getIconNameForMdiWindow(); + QString getTitleForMdiWindow(); + void createActions(); + void setupDefShortcuts(); + bool restoreSessionNextTime(); + QToolBar* getToolBar(int toolbar) const; + + private: + void init(); + void newTable(); + void parseDdl(); + void initDbAndTable(); + void setupCoverWidget(); + void createStructureActions(); + void createDataGridActions(); + void createDataFormActions(); + void createIndexActions(); + void createTriggerActions(); + void editColumn(const QModelIndex& idx); + void delColumn(const QModelIndex& idx); + void editConstraint(const QModelIndex& idx); + void delConstraint(const QModelIndex& idx); + void executeStructureChanges(); + void updateAfterInit(); + QModelIndex structureCurrentIndex() const; + void addConstraint(ConstraintDialog::Constraint mode); + bool validate(bool skipWarning = false); + bool isModified() const; + TokenList indexColumnTokens(SqliteCreateIndexPtr index); + QString getCurrentIndex() const; + QString getCurrentTrigger() const; + void applyInitialTab(); + + int newTableWindowNum = 1; + + Db* db = nullptr; + QString database; + QString table; + Ui::TableWindow *ui = nullptr; + SqlTableModel* dataModel = nullptr; + bool dataLoaded = false; + bool existingTable = true; + SqliteCreateTablePtr createTable; + SqliteCreateTablePtr originalCreateTable; + TableStructureModel* structureModel = nullptr; + TableConstraintsModel* structureConstraintsModel = nullptr; + ConstraintTabModel* constraintTabModel = nullptr; + WidgetCover* widgetCover = nullptr; + ChainExecutor* structureExecutor = nullptr; + TableModifier* tableModifier = nullptr; + bool modifyingThisTable = false; + + private slots: + void executionSuccessful(); + void executionFailed(const QString& errorText); + void dbClosedFinalCleanup(); + void checkIfTableDeleted(const QString& database, const QString& object, DbObjectType type); + void checkIfIndexDeleted(const QString& object); + void checkIfTriggerDeleted(const QString& object); + void refreshStructure(); + void commitStructure(bool skipWarning = false); + void changesSuccessfullyCommited(); + void changesFailedToCommit(int errorCode, const QString& errorText); + void rollbackStructure(); + void editColumn(); + void delColumn(); + void moveColumnUp(); + void moveColumnDown(); + void addConstraint(); + void editConstraint(); + void delConstraint(); + void moveConstraintUp(); + void moveConstraintDown(); + void addPk(); + void addFk(); + void addUnique(); + void addCheck(); + void exportTable(); + void importTable(); + void populateTable(); + void createSimilarTable(); + void tabChanged(int newTab); + void updateStructureToolbarState(); + void updateStructureCommitState(); + void updateTableConstraintsToolbarState(); + void updateDdlTab(); + void updateNewTableState(); + void on_structureView_doubleClicked(const QModelIndex &index); + void on_tableConstraintsView_doubleClicked(const QModelIndex &index); + void nameChanged(); + void withOutRowIdChanged(); + void addIndex(); + void editIndex(); + void delIndex(); + void addTrigger(); + void editTrigger(); + void delTrigger(); + void updateIndexesState(); + void updateTriggersState(); + void nextTab(); + void prevTab(); + + public slots: + void updateIndexes(); + void updateTriggers(); + void addColumn(); + void editColumn(const QString& columnName); + void delColumn(const QString& columnName); + + signals: + void modifyStatusChanged(); +}; + +#endif // TABLEWINDOW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui new file mode 100644 index 0000000..8c46443 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui @@ -0,0 +1,307 @@ + + + TableWindow + + + + 0 + 0 + 609 + 415 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + Structure + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Table name: + + + + + + + + 200 + 16777215 + + + + + + + + WITHOUT ROWID + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + false + + + + true + + + false + + + QAbstractItemView::InternalMove + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + true + + + false + + + QAbstractItemView::InternalMove + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + + + + + Data + + + + + + QTabWidget::South + + + -1 + + + + + + + + Constraints + + + + + + true + + + + + + + + Indexes + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + + + + + + Triggers + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + DDL + + + + + + true + + + + + + + + + + + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    + + DataView + QTabWidget +
    dataview.h
    + 1 +
    +
    + + +
    diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp new file mode 100644 index 0000000..d54a359 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp @@ -0,0 +1,760 @@ +#include "viewwindow.h" +#include "ui_viewwindow.h" +#include "common/unused.h" +#include "schemaresolver.h" +#include "services/notifymanager.h" +#include "services/dbmanager.h" +#include "mainwindow.h" +#include "mdiarea.h" +#include "sqlitesyntaxhighlighter.h" +#include "datagrid/sqlquerymodel.h" +#include "common/utils_sql.h" +#include "viewmodifier.h" +#include "common/widgetcover.h" +#include "db/chainexecutor.h" +#include "dbtree/dbtree.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "dialogs/messagelistdialog.h" +#include "dbobjectdialogs.h" +#include "dialogs/ddlpreviewdialog.h" +#include "uiconfig.h" +#include "services/config.h" +#include +#include +#include +#include + +CFG_KEYS_DEFINE(ViewWindow) + +ViewWindow::ViewWindow(QWidget *parent) : + MdiChild(parent), + ui(new Ui::ViewWindow) +{ + init(); + applyInitialTab(); +} + +ViewWindow::ViewWindow(Db* db, QWidget* parent) : + MdiChild(parent), + db(db), + ui(new Ui::ViewWindow) +{ + newView(); + init(); + applyInitialTab(); +} + +ViewWindow::ViewWindow(const ViewWindow& win) : + MdiChild(win.parentWidget()), + db(win.db), + database(win.database), + view(win.view), + ui(new Ui::ViewWindow) +{ + init(); + initView(); + applyInitialTab(); +} + +ViewWindow::ViewWindow(QWidget* parent, Db* db, const QString& database, const QString& view) : + MdiChild(parent), + db(db), + database(database), + view(view), + ui(new Ui::ViewWindow) +{ + init(); + initView(); + applyInitialTab(); +} + +ViewWindow::~ViewWindow() +{ + delete ui; +} + +void ViewWindow::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +QVariant ViewWindow::saveSession() +{ + QHash sessionValue; + sessionValue["view"] = view; + sessionValue["db"] = db->getName(); + return sessionValue; +} + +bool ViewWindow::restoreSession(const QVariant& sessionValue) +{ + QHash value = sessionValue.toHash(); + if (value.size() == 0) + { + notifyWarn("Could not restore window, because no database or view was stored in session for this window."); + return false; + } + + if (!value.contains("db") || !value.contains("view")) + { + notifyWarn("Could not restore window, because no database or view was stored in session for this window."); + return false; + } + + db = DBLIST->getByName(value["db"].toString()); + if (!db) + { + notifyWarn(tr("Could not restore window, because database %1 could not be resolved.").arg(value["db"].toString())); + return false; + } + + if (!db->isOpen() && !db->open()) + { + notifyWarn(tr("Could not restore window, because database %1 could not be open.").arg(value["db"].toString())); + return false; + } + + view = value["view"].toString(); + database = value["database"].toString(); + SchemaResolver resolver(db); + if (!resolver.getViews(database).contains(view, Qt::CaseInsensitive)) + { + notifyWarn(tr("Could not restore window, because the view %1 doesn't exist in the database %2.").arg(view).arg(db->getName())); + return false; + } + + initView(); + applyInitialTab(); + return true; +} + +Icon* ViewWindow::getIconNameForMdiWindow() +{ + return ICONS.VIEW; +} + +QString ViewWindow::getTitleForMdiWindow() +{ + QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")")); + if (existingView) + return view + dbSuffix; + + QStringList existingNames = MDIAREA->getWindowTitles(); + if (existingNames.contains(windowTitle())) + return windowTitle(); + + // Generate new name + QString title = tr("New view %1").arg(newViewWindowNum++); + while (existingNames.contains(title)) + title = tr("New view %1").arg(newViewWindowNum++); + + title += dbSuffix; + return title; +} + +void ViewWindow::createActions() +{ + createQueryTabActions(); + createTriggersTabActions(); + + createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this); + createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this); +} + +void ViewWindow::setupDefShortcuts() +{ + // Widget context + setShortcutContext({ + REFRESH_TRIGGERS, + ADD_TRIGGER, + EDIT_TRIGGER, + DEL_TRIGGER, + }, + Qt::WidgetWithChildrenShortcut); + + BIND_SHORTCUTS(ViewWindow, Action); +} + +bool ViewWindow::restoreSessionNextTime() +{ + return existingView; +} + +QToolBar* ViewWindow::getToolBar(int toolbar) const +{ + switch (static_cast(toolbar)) + { + case TOOLBAR_QUERY: + return ui->queryToolbar; + case TOOLBAR_TRIGGERS: + return ui->triggersToolbar; + } + return nullptr; +} + +void ViewWindow::init() +{ + ui->setupUi(this); + + dataModel = new SqlQueryModel(this); + ui->dataView->init(dataModel); + + ui->queryEdit->setVirtualSqlExpression("CREATE VIEW name AS %1"); + ui->queryEdit->setDb(db); + + connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful())); + connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString))); + connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); + connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateQueryToolbarStatus())); + connect(ui->queryEdit, SIGNAL(textChanged()), this, SLOT(updateQueryToolbarStatus())); + connect(ui->queryEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateQueryToolbarStatus())); + connect(ui->triggersList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState())); + + structureExecutor = new ChainExecutor(this); + connect(structureExecutor, SIGNAL(success()), this, SLOT(changesSuccessfullyCommited())); + connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString))); + + setupCoverWidget(); + + initActions(); + + refreshTriggers(); + updateQueryToolbarStatus(); + updateTriggersState(); +} + +void ViewWindow::newView() +{ + existingView = false; + view = ""; +} + +void ViewWindow::initView() +{ + ui->nameEdit->setText(view); + + parseDdl(); + + if (!createView) + return; // error occured while parsing ddl, window will be closed + + if (existingView) + { + dataModel->setDb(db); + dataModel->setQuery(originalCreateView->select->detokenize()); + } + + ui->queryEdit->setDb(db); + ui->queryEdit->setPlainText(createView->select->detokenize()); + updateDdlTab(); + + ui->ddlEdit->setSqliteVersion(db->getVersion()); + + refreshTriggers(); + + connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfViewDeleted(QString,QString,DbObjectType))); +} + +void ViewWindow::setupCoverWidget() +{ + widgetCover = new WidgetCover(this); + widgetCover->hide(); + connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt())); +} + +void ViewWindow::createQueryTabActions() +{ + createAction(REFRESH_QUERY, ICONS.RELOAD, tr("Refresh the view", "view window"), this, SLOT(refreshView()), ui->queryToolbar); + ui->queryToolbar->addSeparator(); + createAction(COMMIT_QUERY, ICONS.COMMIT, tr("Commit the view changes", "view window"), this, SLOT(commitView()), ui->queryToolbar); + createAction(ROLLBACK_QUERY, ICONS.ROLLBACK, tr("Rollback the view changes", "view window"), this, SLOT(rollbackView()), ui->queryToolbar); + ui->queryToolbar->addSeparator(); + ui->queryToolbar->addAction(ui->queryEdit->getAction(SqlEditor::FORMAT_SQL)); +} + +void ViewWindow::createTriggersTabActions() +{ + createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "view window"), this, SLOT(refreshTriggers()), ui->triggersToolbar, ui->triggersList); + ui->triggersToolbar->addSeparator(); + createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create new triger", "view window"), this, SLOT(addTrigger()), ui->triggersToolbar, ui->triggersList); + createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit selected triger", "view window"), this, SLOT(editTrigger()), ui->triggersToolbar, ui->triggersList); + createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete selected triger", "view window"), this, SLOT(deleteTrigger()), ui->triggersToolbar, ui->triggersList); +} +QString ViewWindow::getView() const +{ + return view; +} + +void ViewWindow::setSelect(const QString &selectSql) +{ + ui->queryEdit->setPlainText(selectSql); +} + +bool ViewWindow::isUncommited() const +{ + return ui->dataView->isUncommited() || isModified(); +} + +QString ViewWindow::getQuitUncommitedConfirmMessage() const +{ + QString title = getMdiWindow()->windowTitle(); + if (ui->dataView->isUncommited() && isModified()) + return tr("View window \"%1\" has uncommited structure modifications and data.").arg(title); + else if (ui->dataView->isUncommited()) + return tr("View window \"%1\" has uncommited data.").arg(title); + else if (isModified()) + return tr("View window \"%1\" has uncommited structure modifications.").arg(title); + else + { + qCritical() << "Unhandled message case in ViewWindow::getQuitUncommitedConfirmMessage()."; + return QString(); + } +} + +Db* ViewWindow::getAssociatedDb() const +{ + return db; +} + +void ViewWindow::staticInit() +{ + qRegisterMetaType("ViewWindow"); +} + +void ViewWindow::insertAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertAction(action, toolbar); +} + +void ViewWindow::insertActionBefore(ExtActionPrototype* action, ViewWindow::Action beforeAction, ViewWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); +} + +void ViewWindow::insertActionAfter(ExtActionPrototype* action, ViewWindow::Action afterAction, ViewWindow::ToolBar toolbar) +{ + return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); +} + +void ViewWindow::removeAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar) +{ + ExtActionContainer::removeAction(action, toolbar); +} + +QString ViewWindow::getDatabase() const +{ + return database; +} + +Db* ViewWindow::getDb() const +{ + return db; +} + +void ViewWindow::refreshView() +{ + initView(); + updateTriggersState(); +} + +void ViewWindow::commitView(bool skipWarnings) +{ + if (!isModified()) + { + qWarning() << "Called ViewWindow::commitView(), but isModified() returned false."; + updateQueryToolbarStatus(); + return; + } + + if (!validate(skipWarnings)) + return; + + executeStructureChanges(); +} + +void ViewWindow::rollbackView() +{ + createView = SqliteCreateViewPtr::create(*originalCreateView.data()); + ui->nameEdit->setText(createView->view); + ui->queryEdit->setPlainText(createView->select->detokenize()); + + updateQueryToolbarStatus(); + updateDdlTab(); +} + +QString ViewWindow::getCurrentTrigger() const +{ + int row = ui->triggersList->currentRow(); + QTableWidgetItem* item = ui->triggersList->item(row, 0); + if (!item) + return QString::null; + + return item->text(); +} + +void ViewWindow::applyInitialTab() +{ + if (existingView && !view.isNull() && CFG_UI.General.OpenViewsOnData.get()) + ui->tabWidget->setCurrentIndex(1); + else + ui->tabWidget->setCurrentIndex(0); +} + +void ViewWindow::addTrigger() +{ + DbObjectDialogs dialogs(db, this); + dialogs.addTriggerOnView(view); + refreshTriggers(); +} + +void ViewWindow::editTrigger() +{ + QString trigger = getCurrentTrigger(); + if (trigger.isNull()) + return; + + DbObjectDialogs dialogs(db, this); + dialogs.editTrigger(trigger); + refreshTriggers(); +} + +void ViewWindow::deleteTrigger() +{ + QString trigger = getCurrentTrigger(); + if (trigger.isNull()) + return; + + DbObjectDialogs dialogs(db, this); + dialogs.dropObject(trigger); + refreshTriggers(); +} + +void ViewWindow::executionSuccessful() +{ + modifyingThisView = false; + dataLoaded = true; +} + +void ViewWindow::executionFailed(const QString& errorMessage) +{ + modifyingThisView = false; + notifyError(tr("Could not load data for view %1. Error details: %2").arg(view).arg(errorMessage)); +} + +void ViewWindow::tabChanged(int tabIdx) +{ + switch (tabIdx) + { + case 1: + { + if (isModified()) + { + int res = QMessageBox::question(this, tr("Uncommited changes"), + tr("There are uncommited structure modifications. You cannot browse or edit data until you have " + "the view structure settled.\n" + "Do you want to commit the structure, or do you want to go back to the structure tab?"), + tr("Go back to structure tab"), tr("Commit modifications and browse data.")); + + ui->tabWidget->setCurrentIndex(0); + if (res == 1) + commitView(true); + + break; + } + + if (!dataLoaded) + ui->dataView->refreshData(); + + break; + } + case 3: + { + updateDdlTab(); + break; + } + } +} + +void ViewWindow::updateQueryToolbarStatus() +{ + bool modified = isModified(); + bool queryOk = ui->queryEdit->isSyntaxChecked() && !ui->queryEdit->haveErrors(); + actionMap[COMMIT_QUERY]->setEnabled(modified && queryOk); + actionMap[ROLLBACK_QUERY]->setEnabled(modified && existingView); + actionMap[REFRESH_QUERY]->setEnabled(existingView); +} + +void ViewWindow::changesSuccessfullyCommited() +{ + QStringList sqls = structureExecutor->getQueries(); + CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath()); + + widgetCover->hide(); + + originalCreateView = createView; + dataLoaded = false; + + //QString oldView = view; // uncomment when implementing notify manager call + database = createView->database; + view = createView->view; + existingView = true; + initView(); + updateQueryToolbarStatus(); + updateWindowTitle(); + + DBTREE->refreshSchema(db); +} + +void ViewWindow::changesFailedToCommit(int errorCode, const QString& errorText) +{ + qDebug() << "ViewWindow::changesFailedToCommit:" << errorCode << errorText; + + widgetCover->hide(); + + NotifyManager::getInstance()->error(tr("Could not commit view changes. Error message: %1", "view window").arg(errorText)); +} + +void ViewWindow::updateTriggersState() +{ + bool editDel = ui->triggersList->currentItem() != nullptr; + actionMap[REFRESH_TRIGGERS]->setEnabled(existingView); + actionMap[ADD_TRIGGER]->setEnabled(existingView); + actionMap[EDIT_TRIGGER]->setEnabled(editDel); + actionMap[DEL_TRIGGER]->setEnabled(editDel); +} + +void ViewWindow::nextTab() +{ + int idx = ui->tabWidget->currentIndex(); + idx++; + ui->tabWidget->setCurrentIndex(idx); +} + +void ViewWindow::prevTab() +{ + int idx = ui->tabWidget->currentIndex(); + idx--; + ui->tabWidget->setCurrentIndex(idx); +} + +void ViewWindow::dbClosedFinalCleanup() +{ + dataModel->setDb(nullptr); + ui->queryEdit->setDb(nullptr); + structureExecutor->setDb(nullptr); +} + +void ViewWindow::checkIfViewDeleted(const QString& database, const QString& object, DbObjectType type) +{ + UNUSED(database); + + if (type == DbObjectType::TRIGGER) + { + for (int i = 0, total = ui->triggersList->rowCount(); i < total; ++i) + { + if (ui->triggersList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0) + { + ui->triggersList->removeRow(i); + return; + } + } + } + + if (type != DbObjectType::VIEW) + return; + + if (modifyingThisView) + return; + + // TODO uncomment below when dbnames are supported +// if (this->database != database) +// return; + + if (object.compare(view, Qt::CaseInsensitive) == 0) + { + dbClosedFinalCleanup(); + getMdiWindow()->close(); + } +} + +void ViewWindow::refreshTriggers() +{ + if (!db || !db->isValid()) + return; + + SchemaResolver resolver(db); + QList triggers = resolver.getParsedTriggersForView(database, view); + + ui->triggersList->setColumnCount(4); + ui->triggersList->setRowCount(triggers.size()); + ui->triggersList->horizontalHeader()->setMaximumSectionSize(200); + ui->triggersList->setHorizontalHeaderLabels({ + tr("Name", "view window triggers"), + tr("Instead of", "view window triggers"), + tr("Condition", "view window triggers"), + tr("Details", "table window triggers") + }); + + QTableWidgetItem* item = nullptr; + QString event; + int row = 0; + foreach (SqliteCreateTriggerPtr trig, triggers) + { + item = new QTableWidgetItem(trig->trigger); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->triggersList->setItem(row, 0, item); + + event = trig->tokensMap["trigger_event"].detokenize(); + item = new QTableWidgetItem(event); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->triggersList->setItem(row, 1, item); + + item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : ""); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->triggersList->setItem(row, 2, item); + + item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed()); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); + ui->triggersList->setItem(row, 3, item); + + row++; + } + + ui->triggersList->resizeColumnsToContents(); + updateTriggersState(); +} + +void ViewWindow::parseDdl() +{ + if (existingView) + { + SchemaResolver resolver(db); + SqliteQueryPtr parsedObject = resolver.getParsedObject(database, view, SchemaResolver::VIEW); + if (!parsedObject.dynamicCast()) + { + notifyError(tr("Could not process the %1 view correctly. Unable to open a view window.").arg(view)); + invalid = true; + return; + } + + createView = parsedObject.dynamicCast(); + } + else + { + createView = SqliteCreateViewPtr::create(); + createView->view = view; + createView->dialect = db->getDialect(); + } + originalCreateView = SqliteCreateViewPtr::create(*createView); + originalQuery = originalCreateView->select->detokenize(); +} + +void ViewWindow::updateDdlTab() +{ + QString ddl = "CREATE VIEW %1 AS %2"; + ui->ddlEdit->setPlainText(ddl.arg(wrapObjIfNeeded(ui->nameEdit->text(), db->getDialect())).arg(ui->queryEdit->toPlainText())); +} + +bool ViewWindow::isModified() const +{ + return (originalCreateView && originalCreateView->view != ui->nameEdit->text()) || + ui->queryEdit->toPlainText() != originalQuery || + !existingView; +} + +bool ViewWindow::validate(bool skipWarnings) +{ + if (!existingView && !skipWarnings && ui->nameEdit->text().isEmpty()) + { + int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the view is allowed in SQLite, but it is not recommended.\n" + "Are you sure you want to create a view with blank name?"), QMessageBox::Yes, QMessageBox::No); + + if (res != QMessageBox::Yes) + return false; + } + + // Rebuilding createView statement and validating it on the fly. + QString ddl = "CREATE VIEW %1 AS %2"; + QString viewName = wrapObjIfNeeded(ui->nameEdit->text(), db->getDialect()); + QString select = ui->queryEdit->toPlainText(); + + Parser parser(db->getDialect()); + if (!parser.parse(ddl.arg(viewName).arg(select)) || parser.getQueries().size() < 1) + { + notifyError(tr("The SELECT statement could not be parsed. Please correct the query and retry.\nDetails: %1").arg(parser.getErrorString())); + return false; + } + + SqliteQueryPtr query = parser.getQueries().first(); + SqliteCreateViewPtr viewStmt = query.dynamicCast(); + if (!viewStmt) + { + notifyError(tr("The view could not be modified due to internal SQLiteStudio error. Please report this!")); + qCritical() << "Could not parse new view, because parsed object is of different type. The type is" + << sqliteQueryTypeToString(query->queryType) << "for following query:" << ddl; + return false; + } + + createView = viewStmt; + return true; +} + +void ViewWindow::executeStructureChanges() +{ + QStringList sqls; + QList sqlMandatoryFlags; + + createView->rebuildTokens(); + if (!existingView) + { + sqls << createView->detokenize(); + } + else + { + if (viewModifier) + delete viewModifier; + + viewModifier = new ViewModifier(db, database, view); + viewModifier->alterView(createView); + + if (viewModifier->hasMessages()) + { + MessageListDialog dialog(tr("Following problems will take place while modifying the view.\n" + "Would you like to proceed?", "view window")); + dialog.setWindowTitle(tr("View modification", "view window")); + foreach (const QString& error, viewModifier->getErrors()) + dialog.addError(error); + + foreach (const QString& warn, viewModifier->getWarnings()) + dialog.addWarning(warn); + + if (dialog.exec() != QDialog::Accepted) + return; + } + + sqls = viewModifier->generateSqls(); + sqlMandatoryFlags = viewModifier->getMandatoryFlags(); + } + + if (!CFG_UI.General.DontShowDdlPreview.get()) + { + DdlPreviewDialog dialog(db, this); + dialog.setDdl(sqls); + if (dialog.exec() != QDialog::Accepted) + return; + } + + modifyingThisView = true; + structureExecutor->setDb(db); + structureExecutor->setQueries(sqls); + structureExecutor->setMandatoryQueries(sqlMandatoryFlags); + structureExecutor->exec(); + widgetCover->show(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h new file mode 100644 index 0000000..6b50135 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h @@ -0,0 +1,146 @@ +#ifndef VIEWWINDOW_H +#define VIEWWINDOW_H + +#include "mdichild.h" +#include "common/extactioncontainer.h" +#include "db/db.h" +#include "parser/ast/sqlitecreateview.h" +#include "guiSQLiteStudio_global.h" +#include + +namespace Ui { + class ViewWindow; +} + +class SqliteSyntaxHighlighter; +class SqlQueryModel; +class WidgetCover; +class QPushButton; +class QProgressBar; +class ChainExecutor; +class ViewModifier; + +CFG_KEY_LIST(ViewWindow, QObject::tr("A view window"), + CFG_KEY_ENTRY(REFRESH_TRIGGERS, Qt::Key_F5, QObject::tr("Refresh view trigger list")) + CFG_KEY_ENTRY(ADD_TRIGGER, Qt::Key_Insert, QObject::tr("Add new trigger")) + CFG_KEY_ENTRY(EDIT_TRIGGER, Qt::Key_Return, QObject::tr("Edit selected trigger")) + CFG_KEY_ENTRY(DEL_TRIGGER, Qt::Key_Delete, QObject::tr("Delete selected trigger")) + CFG_KEY_ENTRY(NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next tab")) + CFG_KEY_ENTRY(PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous tab")) +) + +class GUI_API_EXPORT ViewWindow : public MdiChild +{ + Q_OBJECT + Q_ENUMS(Action) + + public: + enum Action + { + // Structure tab + REFRESH_QUERY, + COMMIT_QUERY, + ROLLBACK_QUERY, + // Triggers tab + REFRESH_TRIGGERS, + ADD_TRIGGER, + EDIT_TRIGGER, + DEL_TRIGGER, + // All tabs + NEXT_TAB, + PREV_TAB + }; + + enum ToolBar + { + TOOLBAR_QUERY, + TOOLBAR_TRIGGERS + }; + + explicit ViewWindow(QWidget *parent = 0); + ViewWindow(Db* db, QWidget *parent = 0); + ViewWindow(const ViewWindow& win); + ViewWindow(QWidget *parent, Db* db, const QString& database, const QString& view); + ~ViewWindow(); + + Db* getDb() const; + QString getDatabase() const; + QString getView() const; + void setSelect(const QString& selectSql); + bool isUncommited() const; + QString getQuitUncommitedConfirmMessage() const; + Db* getAssociatedDb() const; + + static void staticInit(); + static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_QUERY); + static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_QUERY); + static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_QUERY); + static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_QUERY); + + protected: + void changeEvent(QEvent *e); + QVariant saveSession(); + bool restoreSession(const QVariant& sessionValue); + Icon* getIconNameForMdiWindow(); + QString getTitleForMdiWindow(); + void createActions(); + void setupDefShortcuts(); + bool restoreSessionNextTime(); + QToolBar* getToolBar(int toolbar) const; + + private: + void init(); + void newView(); + void initView(); + void setupCoverWidget(); + void createQueryTabActions(); + void createTriggersTabActions(); + void parseDdl(); + void updateDdlTab(); + bool isModified() const; + bool validate(bool skipWarnings = false); + void executeStructureChanges(); + QString getCurrentTrigger() const; + void applyInitialTab(); + + Db* db = nullptr; + QString database; + QString view; + bool existingView = true; + bool dataLoaded = false; + int newViewWindowNum = 1; + bool modified = false; + SqliteCreateViewPtr originalCreateView; + SqliteCreateViewPtr createView; + SqlQueryModel* dataModel = nullptr; + QString originalQuery; + WidgetCover* widgetCover = nullptr; + ChainExecutor* structureExecutor = nullptr; + ViewModifier* viewModifier = nullptr; + Ui::ViewWindow *ui = nullptr; + bool modifyingThisView = false; + + private slots: + void refreshView(); + void commitView(bool skipWarnings = false); + void rollbackView(); + void addTrigger(); + void editTrigger(); + void deleteTrigger(); + void executionSuccessful(); + void executionFailed(const QString& errorMessage); + void tabChanged(int tabIdx); + void updateQueryToolbarStatus(); + void changesSuccessfullyCommited(); + void changesFailedToCommit(int errorCode, const QString& errorText); + void updateTriggersState(); + void nextTab(); + void prevTab(); + void dbClosedFinalCleanup(); + void checkIfViewDeleted(const QString& database, const QString& object, DbObjectType type); + + public slots: + void refreshTriggers(); +}; + +#endif // VIEWWINDOW_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui new file mode 100644 index 0000000..734a265 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui @@ -0,0 +1,137 @@ + + + ViewWindow + + + + 0 + 0 + 621 + 468 + + + + Form + + + + 0 + + + + + 0 + + + + Query + + + + + + + + + + 0 + + + + + View name: + + + + + + + + + + + + + + + + + Data + + + + + + + + + + Triggers + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + DDL + + + + + + true + + + + + + + + + + + + DataView + QTabWidget +
    dataview.h
    + 1 +
    + + SqlEditor + QPlainTextEdit +
    sqleditor.h
    +
    + + SqlView + QPlainTextEdit +
    sqlview.h
    +
    +
    + + +
    diff --git a/SQLiteStudio3/plugins.pri b/SQLiteStudio3/plugins.pri new file mode 100644 index 0000000..30fd709 --- /dev/null +++ b/SQLiteStudio3/plugins.pri @@ -0,0 +1,138 @@ +CONFIG += c++11 plugin + +DESTDIR = $$PWD/../output/SQLiteStudio/plugins +OBJECTS_DIR = $$PWD/../output/build +MOC_DIR = $$PWD/../output/build +UI_DIR = $$PWD/../output/build + +INCLUDEPATH += $$PWD/coreSQLiteStudio +DEPENDPATH += $$PWD/coreSQLiteStudio + +PLUGINSDIR = $$PWD/../Plugins +INCLUDEPATH += $$PLUGINSDIR +DEPENDPATH += $$PLUGINSDIR + +export (PLUGINSDIR) + +contains(QT, gui) { + INCLUDEPATH += $$PWD/guiSQLiteStudio + INCLUDEPATH += $$UI_DIR/guiSQLiteStudio + DEPENDPATH += $$PWD/guiSQLiteStudio +} + +win32: { + INCLUDEPATH += $$PWD/../../include + LIBS += -L$$PWD/../../lib -L$$DESTDIR/.. -lcoreSQLiteStudio -L$$PWD/../output/SQLiteStudio/plugins + + defineTest(pluginDep) { + linker_flag_parts = -l $$1 + linker_flag = $$join(linker_flag_parts) + LIBS += $$linker_flag + export(LIBS) + } + + defineTest(frameworkDep) { + linker_flag_parts = -l $$1 + linker_flag = $$join(linker_flag_parts) + LIBS += $$linker_flag + export(LIBS) + } + + contains(QT, gui) { + LIBS += -lguiSQLiteStudio + } + + SO_NAME = ddl + SO_PREFIX = lib + PLATFORM = win32 + export(SO_NAME) + export(SO_PREFIX) + export(PLATFORM) +} + +unix: { + defineTest(pluginDep) { + } + + defineTest(frameworkDep) { + linker_flag_parts = -l $$1 + linker_flag = $$join(linker_flag_parts) + LIBS += $$linker_flag + export(LIBS) + } + + target.path = /usr/lib/sqlitestudio + INSTALLS += target +} + +linux: { + SO_NAME = so + SO_PREFIX = lib + PLATFORM = linux32 + equals(QMAKE_HOST.arch, "x86_64") { + PLATFORM = linux64 + } + export(SO_NAME) + export(SO_PREFIX) + export(PLATFORM) +} + +macx: { + GUI_APP = $$PWD/../output/SQLiteStudio/SQLiteStudio.app/Contents/MacOS/SQLiteStudio + export (GUI_APP) + + LIBS += -L$$PWD/../output/SQLiteStudio -lcoreSQLiteStudio + INCLUDEPATH += $$PWD/../../include + LIBS += -L$$PWD/../../lib -L$$DESTDIR + QMAKE_CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=10.7 + + defineTest(pluginDep) { + out_file_parts = $$DESTDIR/lib $$TARGET .dylib + out_file = $$join(out_file_parts) + lib_name_parts = lib $$1 .dylib + lib_name = $$join(lib_name_parts) + QMAKE_POST_LINK += install_name_tool -change $$lib_name @loader_path/../plugins/$$lib_name $$out_file + export(QMAKE_POST_LINK) + + linker_flag_parts = -l $$1 + linker_flag = $$join(linker_flag_parts) + LIBS += $$linker_flag + export(LIBS) + } + + defineTest(frameworkDep) { + out_file_parts = $$DESTDIR/lib $$TARGET .dylib + out_file = $$join(out_file_parts) + lib_name_parts = lib $$1 .dylib + lib_name = $$join(lib_name_parts) + QMAKE_POST_LINK += install_name_tool -change $$lib_name @loader_path/../Frameworks/$$lib_name $$out_file + export(QMAKE_POST_LINK) + + linker_flag_parts = -l $$1 + linker_flag = $$join(linker_flag_parts) + LIBS += $$linker_flag + export(LIBS) + } + + SO_NAME = dylib + SO_PREFIX = lib + PLATFORM = macosx + export(SO_NAME) + export(SO_PREFIX) + export(PLATFROM) +} + +win32|macx: { + CONFIG += portable + + contains(QT, gui) { + LIBS += -lguiSQLiteStudio + } +} + +portable { + QMAKE_LFLAGS += -Wl,-rpath,. -Wl,-rpath,.. + linux: { + QMAKE_LFLAGS += -Wl,-rpath,../lib + } +} diff --git a/SQLiteStudio3/sqlitestudio/SQLiteStudio.exe.manifest b/SQLiteStudio3/sqlitestudio/SQLiteStudio.exe.manifest new file mode 100644 index 0000000..cca0bc6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudio/SQLiteStudio.exe.manifest @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/SQLiteStudio3/sqlitestudio/main.cpp b/SQLiteStudio3/sqlitestudio/main.cpp new file mode 100644 index 0000000..b2d7b94 --- /dev/null +++ b/SQLiteStudio3/sqlitestudio/main.cpp @@ -0,0 +1,142 @@ +#include "mainwindow.h" +#include "iconmanager.h" +#include "dbtree/dbtreeitem.h" +#include "datagrid/sqlquerymodelcolumn.h" +#include "datagrid/sqlquerymodel.h" +#include "sqleditor.h" +#include "windows/editorwindow.h" +#include "windows/tablewindow.h" +#include "windows/viewwindow.h" +#include "dataview.h" +#include "dbtree/dbtree.h" +#include "multieditor/multieditordatetime.h" +#include "multieditor/multieditortime.h" +#include "multieditor/multieditordate.h" +#include "multieditor/multieditorbool.h" +#include "uiconfig.h" +#include "sqlitestudio.h" +#include "uidebug.h" +#include "completionhelper.h" +#include "services/updatemanager.h" +#include "guiSQLiteStudio_global.h" +#include "coreSQLiteStudio_global.h" +#include "log.h" +#include "qio.h" +#include "services/pluginmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include + +bool listPlugins = false; + +QString uiHandleCmdLineArgs() +{ + QCommandLineParser parser; + parser.setApplicationDescription(QObject::tr("GUI interface to SQLiteStudio, a SQLite manager.")); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption debugOption({"d", "debug"}, QObject::tr("Enables debug messages in console (accessible with F12).")); + QCommandLineOption debugStdOutOption("debug-stdout", QObject::tr("Redirects debug messages into standard output (forces debug mode).")); + QCommandLineOption lemonDebugOption("debug-lemon", QObject::tr("Enables Lemon parser debug messages for SQL code assistant.")); + QCommandLineOption sqlDebugOption("debug-sql", QObject::tr("Enables debugging of every single SQL query being sent to any database.")); + QCommandLineOption sqlDebugDbNameOption("debug-sql-db", QObject::tr("Limits SQL query messages to only the given ."), QObject::tr("database")); + QCommandLineOption listPluginsOption("list-plugins", QObject::tr("Lists plugins installed in the SQLiteStudio end exists.")); + parser.addOption(debugOption); + parser.addOption(debugStdOutOption); + parser.addOption(lemonDebugOption); + parser.addOption(sqlDebugOption); + parser.addOption(sqlDebugDbNameOption); + parser.addOption(listPluginsOption); + + parser.addPositionalArgument(QObject::tr("file"), QObject::tr("Database file to open")); + + parser.process(qApp->arguments()); + + setUiDebug(parser.isSet(debugOption) || parser.isSet(debugStdOutOption) || parser.isSet(sqlDebugOption), !parser.isSet(debugStdOutOption)); + CompletionHelper::enableLemonDebug = parser.isSet(lemonDebugOption); + setSqlLoggingEnabled(parser.isSet(sqlDebugOption)); + if (parser.isSet(sqlDebugDbNameOption)) + setSqlLoggingFilter(parser.value(sqlDebugDbNameOption)); + + if (parser.isSet(listPluginsOption)) + listPlugins = true; + + QStringList args = parser.positionalArguments(); + if (args.size() > 0) + return args[0]; + + return QString::null; +} + +bool updateRetryFunction(const QString& msg) +{ + QMessageBox mb(QMessageBox::Critical, QObject::tr("Error"), msg); + mb.addButton(QMessageBox::Retry); + mb.addButton(QMessageBox::Abort); + return (mb.exec() == QMessageBox::Retry); +} + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + int retCode = 1; + UpdateManager::setRetryFunction(updateRetryFunction); + if (UpdateManager::handleUpdateOptions(a.arguments(), retCode)) + { + if (retCode) + QMessageBox::critical(nullptr, QObject::tr("Error"), UpdateManager::getStaticErrorMessage()); + + return retCode; + } + + qInstallMessageHandler(uiMessageHandler); + + QString dbToOpen = uiHandleCmdLineArgs(); + + DbTreeItem::initMeta(); + SqlQueryModelColumn::initMeta(); + SqlQueryModel::staticInit(); + + SQLITESTUDIO->init(a.arguments(), true); + IconManager::getInstance()->init(); + DbTree::staticInit(); + DataView::staticInit(); + EditorWindow::staticInit(); + TableWindow::staticInit(); + ViewWindow::staticInit(); + MultiEditorDateTime::staticInit(); + MultiEditorTime::staticInit(); + MultiEditorDate::staticInit(); + MultiEditorBool::staticInit(); + + MainWindow::getInstance(); + + SQLITESTUDIO->initPlugins(); + + if (listPlugins) + { + for (const PluginManager::PluginDetails& details : PLUGINS->getAllPluginDetails()) + qOut << details.name << " " << details.versionString << "\n"; + + return 0; + } + + IconManager::getInstance()->rescanResources(); + + MainWindow::getInstance()->restoreSession(); + MainWindow::getInstance()->show(); + + if (!dbToOpen.isNull()) + MainWindow::getInstance()->openDb(dbToOpen); + + UPDATES->checkForUpdates(); + + return a.exec(); +} diff --git a/SQLiteStudio3/sqlitestudio/sqlitestudio.pro b/SQLiteStudio3/sqlitestudio/sqlitestudio.pro new file mode 100644 index 0000000..07be99c --- /dev/null +++ b/SQLiteStudio3/sqlitestudio/sqlitestudio.pro @@ -0,0 +1,46 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-09-06T00:39:26 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +include($$PWD/../dirs.pri) +include($$PWD/../utils.pri) + +OBJECTS_DIR = $$OBJECTS_DIR/sqlitestudio +MOC_DIR = $$MOC_DIR/sqlitestudio +UI_DIR = $$UI_DIR/sqlitestudio + +linux: { + TARGET = sqlitestudio +} +!linux: { + TARGET = SQLiteStudio +} +TEMPLATE = app + +CONFIG += c++11 +QMAKE_CXXFLAGS += -pedantic +linux|portable { + QMAKE_LFLAGS += -Wl,-rpath,./lib +} + +LIBS += -lcoreSQLiteStudio -lguiSQLiteStudio + +SOURCES += main.cpp + +win32: { + RC_FILE = windows.rc +} + +macx: { + ICON = ../guiSQLiteStudio/img/sqlitestudio.icns +} + +OTHER_FILES += \ + windows.rc \ + SQLiteStudio.exe.manifest diff --git a/SQLiteStudio3/sqlitestudio/windows.rc b/SQLiteStudio3/sqlitestudio/windows.rc new file mode 100644 index 0000000..b22d93b --- /dev/null +++ b/SQLiteStudio3/sqlitestudio/windows.rc @@ -0,0 +1,4 @@ +#include + +ID_ICON ICON DISCARDABLE "../guiSQLiteStudio/img/sqlitestudio.ico" +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "SQLiteStudio.exe.manifest" diff --git a/SQLiteStudio3/sqlitestudiocli/cli.cpp b/SQLiteStudio3/sqlitestudiocli/cli.cpp new file mode 100644 index 0000000..a663e84 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cli.cpp @@ -0,0 +1,321 @@ +#include "cli.h" +#include "services/config.h" +#include "cli_config.h" +#include "services/dbmanager.h" +#include "commands/clicommandfactory.h" +#include "commands/clicommand.h" +#include "qio.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "climsghandler.h" +#include "clicompleter.h" +#include +#include +#include +#include +#include +#include + +#if defined(Q_OS_WIN32) +#include "readline.h" +#elif defined(Q_OS_UNIX) +#include +#include +#endif + +CLI* CLI::instance = nullptr; + +CLI::CLI(QObject* parent) : + QObject(parent) +{ + setCurrentDb(nullptr); + + using_history(); + +#ifdef Q_OS_UNIX + history_base = 0; // for some reason this was set to 1 under Unix, making 1st history entry to be always ommited +#endif + + + loadHistory(); + CliCompleter::getInstance()->init(this); +} + +CLI::~CLI() +{ +} + +CLI* CLI::getInstance() +{ + if (!instance) + instance = new CLI(); + + return instance; +} + +void CLI::start() +{ + thread = new QThread(this); + + CliCommandFactory::init(); + + connect(thread, &QThread::started, this, &CLI::doWork); + connect(thread, &QThread::finished, this, &CLI::done); + this->moveToThread(thread); + + if (!getCurrentDb()) // it could be set by openDbFile() from main(). + { + Db* db = DBLIST->getByName(CFG_CLI.Console.DefaultDatabase.get()); + if (db) + { + setCurrentDb(db); + } + else + { + QList dbList = DBLIST->getDbList(); + if (dbList.size() > 0) + setCurrentDb(dbList[0]); + else + setCurrentDb(nullptr); + } + } + + qOut << QString("\n%1 (%2)\n------------------------\n\n").arg(QCoreApplication::applicationName()).arg(QCoreApplication::applicationVersion()); + qOut.flush(); + + if (getCurrentDb()) + qOut << tr("Current database: %1").arg(getCurrentDb()->getName()) << "\n"; + else + qOut << tr("No current working database is set.") << "\n"; + + qOut << tr("Type %1 for help").arg(".help") << "\n\n"; + + qOut.flush(); + + thread->start(); +} + +void CLI::setCurrentDb(Db* db) +{ + currentDb = db; + if (db && !db->isOpen()) + db->open(); +} + +Db* CLI::getCurrentDb() const +{ + return currentDb; +} + +void CLI::exit() +{ + doExit = true; +} + +QStringList CLI::getHistory() const +{ + QStringList cfgHistory; + + int length = historyLength(); + + QString line; + HIST_ENTRY* entry = nullptr; + for (int i = 0; i < length; i++) + { + entry = history_get(i); + if (!entry) + { + qWarning() << "Null history entry for i =" << i; + continue; + } + + line = QString::fromLocal8Bit(entry->line); + if (line.isEmpty()) + continue; + + cfgHistory << line; + } + return cfgHistory; +} + +void CLI::println(const QString &msg) +{ + qOut << msg << "\n"; + qOut.flush(); +} + +int CLI::historyLength() const +{ +#if defined(Q_OS_WIN) + return history_length(); +#elif defined(Q_OS_UNIX) + return history_length; +#endif +} + +void CLI::waitForExecution() +{ + executionFinished = false; + while (!executionFinished) + { + qApp->processEvents(); + QThread::usleep(20); + } +} + +bool CLI::isComplete(const QString& contents) const +{ + if (contents.startsWith(CFG_CLI.Console.CommandPrefixChar.get())) + return true; + + Dialect dialect = Dialect::Sqlite3; + if (getCurrentDb()) + dialect = getCurrentDb()->getDialect(); + + bool complete = true; + splitQueries(contents, dialect, true, &complete); + return complete; +} + +void CLI::loadHistory() +{ + foreach (const QString& line, CFG->getCliHistory()) + { + if (!line.isEmpty()) + add_history(line.toLocal8Bit().data()); + } +} + +void CLI::addHistory(const QString& text) +{ + if (text == lastHistoryEntry) + return; + + CFG->addCliHistory(text); + + add_history(text.toLocal8Bit().data()); + if (historyLength() > CFG_CORE.Console.HistorySize.get()) +#ifdef Q_OS_OSX + free(remove_history(0)); +#else + free_history_entry(remove_history(0)); +#endif + + lastHistoryEntry = text; +} + +QString CLI::getLine() const +{ + return line; +} + +void CLI::applyHistoryLimit() +{ + CFG->applyCliHistoryLimit(); + while (historyLength() > CFG_CORE.Console.HistorySize.get()) +#ifdef Q_OS_OSX + free(remove_history(0)); +#else + free_history_entry(remove_history(0)); +#endif +} + +void CLI::openDbFile(const QString& path) +{ + QString name = DBLIST->quickAddDb(path, QHash()); + if (name.isNull()) + { + println(tr("Could not add database %1 to list.").arg(path)); + return; + } + Db* db = DBLIST->getByName(name); + setCurrentDb(db); +} + +void CLI::doWork() +{ + static const QString prompt = "%1>"; + + CliCommand* cliCommand = nullptr; + QString cmd; + QStringList cmdArgs; + QString cPrompt; + char *cline = nullptr; + while (!doExit) + { + line.clear(); + + while (!doExit && (line.isEmpty() || !isComplete(line))) + { + if (getCurrentDb()) + { + cPrompt = getCurrentDb()->getName(); + if (!getCurrentDb()->isOpen()) + cPrompt += " ["+tr("closed")+"]"; + + cPrompt = prompt.arg(cPrompt); + } + else + cPrompt = prompt.arg(""); + + if (!line.isEmpty()) + { + cPrompt = pad("->", -cPrompt.length(), ' '); + line += "\n"; + } + + cline = readline(cPrompt.toLocal8Bit().data()); + + line += cline; + free(cline); + } + addHistory(line); + + if (line.startsWith(CFG_CLI.Console.CommandPrefixChar.get())) + { + cmdArgs = tokenizeArgs(line.mid(1)); + cmd = cmdArgs.takeAt(0); + cliCommand = CliCommandFactory::getCommand(cmd); + if (!cliCommand) + { + println("No such command."); + continue; + } + } + else + { + cliCommand = CliCommandFactory::getCommand("query"); + cmdArgs.clear(); + cmdArgs << line; + } + + cliCommand->setup(this); + if (!cliCommand->parseArgs(cmdArgs)) + { + delete cliCommand; + continue; + } + + cliCommand->moveToThread(qApp->thread()); + emit execCommand(cliCommand); + waitForExecution(); + } + + thread->quit(); +} + +void CLI::done() +{ + qApp->exit(); +} + +void CLI::executionComplete() +{ + executionFinished = true; +} + +void CLI::clearHistory() +{ + clear_history(); + CFG->clearCliHistory(); +} diff --git a/SQLiteStudio3/sqlitestudiocli/cli.h b/SQLiteStudio3/sqlitestudiocli/cli.h new file mode 100644 index 0000000..6f7c927 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cli.h @@ -0,0 +1,62 @@ +#ifndef CLI_H +#define CLI_H + +#include "db/db.h" +#include +#include +#include +#include + +class QThread; +class QFile; +class DbManager; +class CliCommand; + +class CLI : public QObject +{ + Q_OBJECT + + public: + ~CLI(); + + static CLI* getInstance(); + + void start(); + void setCurrentDb(Db* db); + Db* getCurrentDb() const; + void exit(); + QStringList getHistory() const; + QString getLine() const; + void applyHistoryLimit(); + + private: + explicit CLI(QObject* parent = nullptr); + + void waitForExecution(); + bool isComplete(const QString& contents) const; + void loadHistory(); + void addHistory(const QString& text); + void println(const QString& msg = QString()); + int historyLength() const; + + static CLI* instance; + + QString lastHistoryEntry; + QThread* thread = nullptr; + Db* currentDb = nullptr; + bool executionFinished = false; + bool doExit = false; + QString line; + + signals: + void execCommand(CliCommand* cmd); + + public slots: + void doWork(); + void done(); + void executionComplete(); + void clearHistory(); + void openDbFile(const QString& path); +}; + +#endif // CLI_H diff --git a/SQLiteStudio3/sqlitestudiocli/cli_config.cpp b/SQLiteStudio3/sqlitestudiocli/cli_config.cpp new file mode 100644 index 0000000..93c2b53 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cli_config.cpp @@ -0,0 +1,55 @@ +#include "cli_config.h" + +CFG_DEFINE(Cli) + +CliResultsDisplay::Mode CliResultsDisplay::mode(const QString& mode) +{ + if (mode == "ROW") + return ROW; + + if (mode == "FIXED") + return FIXED; + + if (mode == "COLUMNS") + return COLUMNS; + + return CLASSIC; +} + +QString CliResultsDisplay::mode(CliResultsDisplay::Mode mode) +{ + switch (mode) + { + case ROW: + return "ROW"; + case FIXED: + return "FIXED"; + case CLASSIC: + return "CLASSIC"; + case COLUMNS: + return "COLUMNS"; + } + return "CLASSIC"; +} + + +void CliResultsDisplay::staticInit() +{ + qRegisterMetaType(); + qRegisterMetaTypeStreamOperators(); +} + + +QDataStream& operator<<(QDataStream& out, const CliResultsDisplay::Mode& mode) +{ + out << static_cast(mode); + return out; +} + +QDataStream& operator>>(QDataStream& in, CliResultsDisplay::Mode& mode) +{ + int modeEnum; + in >> modeEnum; + mode = static_cast(modeEnum); + return in; +} diff --git a/SQLiteStudio3/sqlitestudiocli/cli_config.h b/SQLiteStudio3/sqlitestudiocli/cli_config.h new file mode 100644 index 0000000..7827462 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cli_config.h @@ -0,0 +1,38 @@ +#ifndef CLI_CONFIG_H +#define CLI_CONFIG_H + +#include "config_builder.h" + +namespace CliResultsDisplay +{ + enum Mode + { + CLASSIC = 0, + FIXED = 1, + ROW = 2, + COLUMNS = 3 + }; + + Mode mode(const QString& mode); + QString mode(Mode mode); + void staticInit(); + +} + +QDataStream &operator<<(QDataStream &out, const CliResultsDisplay::Mode& mode); +QDataStream &operator>>(QDataStream &in, CliResultsDisplay::Mode& mode); + +Q_DECLARE_METATYPE(CliResultsDisplay::Mode) + +CFG_CATEGORIES(Cli, + CFG_CATEGORY(Console, + CFG_ENTRY(QString, DefaultDatabase, QString()) + CFG_ENTRY(QString, CommandPrefixChar, ".") + CFG_ENTRY(CliResultsDisplay::Mode, ResultsDisplayMode, CliResultsDisplay::CLASSIC) + CFG_ENTRY(QString, NullValue, "") + ) +) + +#define CFG_CLI CFG_INSTANCE(Cli) + +#endif // CLI_CONFIG_H diff --git a/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.cpp b/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.cpp new file mode 100644 index 0000000..6a3072e --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.cpp @@ -0,0 +1,24 @@ +#include "clicommandexecutor.h" +#include "commands/clicommand.h" + +CliCommandExecutor::CliCommandExecutor(QObject *parent) : + QObject(parent) +{ +} + +void CliCommandExecutor::execCommand(CliCommand* cmd) +{ + connect(cmd, SIGNAL(execComplete()), this, SLOT(asyncExecutionComplete())); + cmd->execute(); + if (!cmd->isAsyncExecution()) + { + delete cmd; + emit executionComplete(); + } +} + +void CliCommandExecutor::asyncExecutionComplete() +{ + sender()->deleteLater(); + emit executionComplete(); +} diff --git a/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.h b/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.h new file mode 100644 index 0000000..0312e51 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.h @@ -0,0 +1,26 @@ +#ifndef CLICOMMANDEXECUTOR_H +#define CLICOMMANDEXECUTOR_H + +#include +#include + +class CliCommand; + +class CliCommandExecutor : public QObject +{ + Q_OBJECT + + public: + explicit CliCommandExecutor(QObject *parent = 0); + + signals: + void executionComplete(); + + public slots: + void execCommand(CliCommand* cmd); + + private slots: + void asyncExecutionComplete(); +}; + +#endif // CLICOMMANDEXECUTOR_H diff --git a/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.cpp b/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.cpp new file mode 100644 index 0000000..732b0f0 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.cpp @@ -0,0 +1,433 @@ +#include "clicommandsyntax.h" +#include "commands/clicommand.h" +#include "commands/clicommandfactory.h" +#include "cli.h" +#include + +CliCommandSyntax::CliCommandSyntax() +{ +} + +CliCommandSyntax::~CliCommandSyntax() +{ + foreach (Argument* arg, arguments) + delete arg; + + arguments.clear(); + argumentMap.clear(); + + foreach (Option* opt, options) + delete opt; + + optionMap.clear(); + optionsLongNameMap.clear(); + optionsShortNameMap.clear(); + options.clear(); +} + +void CliCommandSyntax::addArgument(int id, const QString& name, bool mandatory) +{ + addArgumentInternal(id, {name}, mandatory, Argument::REGULAR); +} + +void CliCommandSyntax::addStrictArgument(int id, const QStringList& values, bool mandatory) +{ + addArgumentInternal(id, values, mandatory, Argument::STRICT); +} + +void CliCommandSyntax::addAlternatedArgument(int id, const QStringList& names, bool mandatory) +{ + addArgumentInternal(id, names, mandatory, Argument::ALTERNATED); +} + +void CliCommandSyntax::addOptionShort(int id, const QString& shortName) +{ + addOptionInternal(id, shortName, QString(), QString()); +} + +void CliCommandSyntax::addOptionLong(int id, const QString& longName) +{ + addOptionInternal(id, QString(), longName, QString()); +} + +void CliCommandSyntax::addOption(int id, const QString& shortName, const QString& longName) +{ + addOptionInternal(id, shortName, longName, QString()); +} + +void CliCommandSyntax::addOptionWithArgShort(int id, const QString& shortName, const QString& argName) +{ + addOptionInternal(id, shortName, QString(), argName); +} + +void CliCommandSyntax::addOptionWithArgLong(int id, const QString& longName, const QString& argName) +{ + addOptionInternal(id, QString(), longName, argName); +} + +void CliCommandSyntax::addOptionWithArg(int id, const QString& shortName, const QString& longName, const QString& argName) +{ + addOptionInternal(id, shortName, longName, argName); +} + +CliCommandSyntax::Argument* CliCommandSyntax::addArgumentInternal(int id, const QStringList& names, bool mandatory, Argument::Type type) +{ + checkNewArgument(mandatory); + + Argument* arg = new Argument; + arg->mandatory = mandatory; + arg->id = id; + arg->type = type; + arg->names = names; + arguments << arg; + argumentMap[id] = arg; + return arg; +} + +CliCommandSyntax::Option* CliCommandSyntax::addOptionInternal(int id, const QString& shortName, const QString& longName, const QString& argName) +{ + Option* opt = new Option; + opt->shortName = shortName; + opt->longName = longName; + opt->id = id; + opt->argName = argName; + optionMap[id] = opt; + options << opt; + optionsShortNameMap[shortName] = opt; + optionsLongNameMap[longName] = opt; + return opt; +} + +bool CliCommandSyntax::getStrictArgumentCount() const +{ + return strictArgumentCount; +} + +void CliCommandSyntax::setStrictArgumentCount(bool value) +{ + strictArgumentCount = value; +} + +bool CliCommandSyntax::parse(const QStringList& args) +{ + pastOptions = (options.size() == 0); + lastParsedOption = nullptr; + QString arg; + bool res = false; + int argCnt = args.size(); + for (int argIdx = 0; argIdx < argCnt; argIdx++) + { + arg = args[argIdx]; + + if (arg == "--help") + { + CliCommand* help = CliCommandFactory::getCommand("help"); + help->setup(CLI::getInstance()); + help->parseArgs({getName()}); + help->execute(); + return false; + } + else if (pastOptions) + { + res = parseArg(arg); + } + else if (arg == "--") + { + pastOptions = true; + res = true; + } + else if (arg.startsWith("-")) + { + res = parseOpt(arg, args, argIdx); + } + else + { + pastOptions = true; + res = parseArg(arg); + } + + if (!res) + return false; + } + + if (strictArgumentCount && argPosition < requiredArguments()) + { + parsingErrorText = QObject::tr("Insufficient number of arguments."); + return false; + } + + return true; +} + +QString CliCommandSyntax::getErrorText() const +{ + return parsingErrorText; +} + +QStringList CliCommandSyntax::getStrictArgumentCandidates() +{ + QStringList results; + if (!pastOptions) + { + if (lastParsedOption && !lastParsedOption->argName.isEmpty()) + return results; // this case is covered by getRegularArgumentCandidates() + + foreach (Option* opt, options) + { + if (opt->requested) + continue; + + if (!opt->shortName.isEmpty()) + results << "-"+opt->shortName; + + if (!opt->longName.isEmpty()) + results << "--"+opt->longName; + } + results << "--"; + } + + if (argPosition < arguments.size() && arguments[argPosition]->type == Argument::STRICT) + results += arguments[argPosition]->names; + + return results; +} + +QList CliCommandSyntax::getRegularArgumentCandidates() +{ + QList results; + + if (!pastOptions && lastParsedOption && !lastParsedOption->argName.isEmpty()) + { + // We're exactly at the spot where the option argument is expected + results << lastParsedOption->id; + return results; + } + + if (argPosition < arguments.size()) + { + switch (arguments[argPosition]->type) + { + case Argument::REGULAR: + case Argument::ALTERNATED: + results << arguments[argPosition]->id; + break; + case Argument::STRICT: + break; + } + } + + return results; +} + +void CliCommandSyntax::addAlias(const QString& alias) +{ + aliases << alias; +} + +QStringList CliCommandSyntax::getAliases() const +{ + return aliases; +} + +QString CliCommandSyntax::getSyntaxDefinition() const +{ + return getSyntaxDefinition(name); +} + +QString CliCommandSyntax::getSyntaxDefinition(const QString& usedName) const +{ + static const QString mandatoryArgTempl = "<%1>"; + static const QString optionalArgTempl = "[<%1>]"; + static const QString mandatoryStrictTempl = "%1"; + static const QString optionalStrictTempl = "[%1]"; + static const QString optionTempl = "[%1]"; + static const QString optionWithArgTempl = "[%1 <%2>]"; + + QStringList words; + words << usedName; + + QString optName; + QStringList optNameParts; + foreach (Option* opt, options) + { + optNameParts.clear();; + if (!opt->shortName.isEmpty()) + optNameParts << "-"+opt->shortName; + + if (!opt->longName.isEmpty()) + optNameParts += "--"+opt->longName; + + optName = optNameParts.join("|"); + + words << (opt->argName.isEmpty() ? optionTempl.arg(optName) : optionWithArgTempl.arg(optName).arg(opt->argName)); + } + + QString templ; + QString argName; + foreach (Argument* arg, arguments) + { + templ = (arg->mandatory ? mandatoryArgTempl : optionalArgTempl); + switch (arg->type) + { + case CliCommandSyntax::Argument::ALTERNATED: + argName = arg->names.join("|"); + break; + case CliCommandSyntax::Argument::REGULAR: + argName = arg->names.first(); + break; + case CliCommandSyntax::Argument::STRICT: + argName = arg->names.join("|"); + templ = (arg->mandatory ? mandatoryStrictTempl : optionalStrictTempl); + break; + } + words << templ.arg(argName); + } + + return words.join(" "); +} + +bool CliCommandSyntax::isArgumentSet(int id) const +{ + if (!argumentMap.contains(id)) + return false; + + return argumentMap[id]->defined; +} + +QString CliCommandSyntax::getArgument(int id) const +{ + if (!argumentMap.contains(id)) + return QString::null; + + return argumentMap[id]->value; +} + +bool CliCommandSyntax::isOptionSet(int id) const +{ + if (!optionMap.contains(id)) + return false; + + return optionMap[id]->requested; +} + +QString CliCommandSyntax::getOptionValue(int id) const +{ + if (!optionMap.contains(id)) + return QString::null; + + return optionMap[id]->value; +} + +bool CliCommandSyntax::parseArg(const QString& arg) +{ + if (strictArgumentCount && arguments.size() < (argPosition + 1)) + { + parsingErrorText = QObject::tr("Too many arguments."); + return false; + } + + switch (arguments[argPosition]->type) + { + case CliCommandSyntax::Argument::ALTERNATED: + { + arguments[argPosition]->value = arg; + arguments[argPosition]->defined = true; + break; + } + case CliCommandSyntax::Argument::REGULAR: + { + arguments[argPosition]->value = arg; + arguments[argPosition]->defined = true; + break; + } + case CliCommandSyntax::Argument::STRICT: + { + if (!arguments[argPosition]->names.contains(arg)) + { + parsingErrorText = QObject::tr("Invalid argument value: %1.\nExpected one of: %2").arg(arg) + .arg(arguments[argPosition]->names.join(", ")); + + return false; + } + arguments[argPosition]->value = arg; + arguments[argPosition]->defined = true; + break; + } + default: + qCritical() << "Invalid argument type in CliCommandSyntax:" << arguments[argPosition]->type; + return false; + } + + argPosition++; + return true; +} + +bool CliCommandSyntax::parseOpt(const QString& arg, const QStringList& args, int& argIdx) +{ + Option* opt = nullptr; + if (arg.startsWith("--")) + { + QString longName = arg.mid(2); + if (optionsLongNameMap.contains(longName)) + opt = optionsLongNameMap.value(longName); + } + else + { + QString shortName = arg.mid(1); + if (optionsShortNameMap.contains(shortName)) + opt = optionsShortNameMap.value(shortName); + } + + if (!opt) + { + parsingErrorText = QObject::tr("Unknown option: %1", "CLI command syntax").arg(arg); + return false; + } + + opt->requested = true; + lastParsedOption = opt; + + if (!opt->argName.isEmpty()) + { + if (args.size() <= (argIdx + 1)) + { + parsingErrorText = QObject::tr("Option %1 requires an argument.", "CLI command syntax").arg(arg); + return false; + } + + argIdx++; + opt->value = args[argIdx]; + } + return true; +} + +int CliCommandSyntax::requiredArguments() const +{ + int cnt = 0; + foreach (Argument* arg, arguments) + { + if (arg->mandatory) + cnt++; + } + return cnt; +} + +void CliCommandSyntax::checkNewArgument(bool mandatory) +{ + if (arguments.size() > 0 && !arguments.last()->mandatory && mandatory) + { + qWarning() << "Adding mandatory CLI command argument after optional argument. This will result in invalid syntax definition. The command is:" + << this->name; + } +} + +QString CliCommandSyntax::getName() const +{ + return name; +} + +void CliCommandSyntax::setName(const QString& value) +{ + name = value; +} + diff --git a/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.h b/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.h new file mode 100644 index 0000000..adf88d2 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.h @@ -0,0 +1,97 @@ +#ifndef CLICOMMANDSYNTAX_H +#define CLICOMMANDSYNTAX_H + +#include +#include +#include + +class CliCommandSyntax +{ + public: + CliCommandSyntax(); + ~CliCommandSyntax(); + + void addArgument(int id, const QString& name, bool mandatory = true); + void addStrictArgument(int id, const QStringList& values, bool mandatory = true); + void addAlternatedArgument(int id, const QStringList& names, bool mandatory = true); + void addOptionShort(int id, const QString& shortName); + void addOptionLong(int id, const QString& longName); + void addOption(int id, const QString& shortName, const QString& longName); + void addOptionWithArgShort(int id, const QString& shortName, const QString& argName); + void addOptionWithArgLong(int id, const QString& longName, const QString& argName); + void addOptionWithArg(int id, const QString& shortName, const QString& longName, const QString& argName); + + bool parse(const QStringList& args); + QString getErrorText() const; + QStringList getStrictArgumentCandidates(); + QList getRegularArgumentCandidates(); + + void addAlias(const QString& alias); + QStringList getAliases() const; + + QString getSyntaxDefinition() const; + QString getSyntaxDefinition(const QString& usedName) const; + + bool isArgumentSet(int id) const; + QString getArgument(int id) const; + + bool isOptionSet(int id) const; + QString getOptionValue(int id) const; + + QString getName() const; + void setName(const QString& value); + + bool getStrictArgumentCount() const; + void setStrictArgumentCount(bool value); + + private: + struct Argument + { + enum Type + { + REGULAR, + STRICT, + ALTERNATED + }; + + int id; + QStringList names; + Type type = REGULAR; + bool mandatory = true; + bool defined = false; + QString value; + }; + + struct Option + { + int id; + QString shortName; + QString longName; + QString argName; + bool requested = false; + QString value; + }; + + bool parseArg(const QString& arg); + bool parseOpt(const QString& arg, const QStringList& args, int& argIdx); + int requiredArguments() const; + void checkNewArgument(bool mandatory); + Argument* addArgumentInternal(int id, const QStringList& names, bool mandatory, Argument::Type type); + Option* addOptionInternal(int id, const QString& shortName, const QString& longName, const QString& argName); + + int argPosition = 0; + QString parsingErrorText; + bool strictArgumentCount = true; + bool pastOptions = false; + QString name; + QStringList aliases; + QList arguments; + QHash argumentMap; + QList options; + Option* lastParsedOption = nullptr; + QHash optionMap; + QHash optionsShortNameMap; + QHash optionsLongNameMap; +}; + +#endif // CLICOMMANDSYNTAX_H diff --git a/SQLiteStudio3/sqlitestudiocli/clicompleter.cpp b/SQLiteStudio3/sqlitestudiocli/clicompleter.cpp new file mode 100644 index 0000000..1c3c99c --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicompleter.cpp @@ -0,0 +1,173 @@ +#include "clicompleter.h" +#include "completionhelper.h" +#include "cli.h" +#include "cli_config.h" +#include "common/unused.h" +#include "commands/clicommand.h" +#include "parser/lexer.h" +#include "commands/clicommandfactory.h" +#include +#include + +#if defined(Q_OS_WIN32) +#include "readline.h" +#elif defined(Q_OS_UNIX) +#include +#endif + +CliCompleter* CliCompleter::instance = nullptr; + +CliCompleter::CliCompleter() +{ +} + +void CliCompleter::init(CLI* value) +{ + rl_attempted_completion_function = CliCompleter::complete; + cli = value; +} + +CliCompleter* CliCompleter::getInstance() +{ + if (!instance) + instance = new CliCompleter(); + + return instance; +} + +char** CliCompleter::complete(const char* text, int start, int end) +{ + UNUSED(start); + +#ifdef Q_OS_UNIX + // Unix readline needs this to disable the completion using rl_completion_entry_function + rl_attempted_completion_over = 1; +#endif + + return toCharArray(getInstance()->completeInternal(QString::fromLocal8Bit(text), QString::fromLocal8Bit(rl_line_buffer), end)); +} + +QStringList CliCompleter::completeInternal(const QString& toBeReplaced, const QString& text, int curPos) +{ + QString str; + if (!cli->getLine().isEmpty()) + { + str += cli->getLine(); + curPos += str.length(); + } + + str += text; + + QStringList list; + if (str.startsWith(CFG_CLI.Console.CommandPrefixChar.get())) + list = completeCommand(str, curPos); + else + list = completeQuery(toBeReplaced, str, curPos); + + list.removeDuplicates(); + +#ifdef Q_OS_WIN + if (list.size() == 1) + list[0] += " "; +#endif + +#ifdef Q_OS_UNIX + // Unix readline treats first element in the list as a common value of all elements + if (list.size() > 0) + list.prepend(longestCommonPart(list)); +#endif + + return list; +} + +QStringList CliCompleter::completeCommand(const QString& str, int curPos) +{ + QStringList results; + QString text = str.mid(0, curPos); + + QStringList cmdWords = tokenizeArgs(text); + if (cmdWords.size() == 0) + return results; + + if (text[text.length()-1].isSpace()) + cmdWords << ""; + + QString cmdStr = cmdWords[0].mid(1); + if (cmdWords.size() > 1) + { + CliCommand* command = CliCommandFactory::getCommand(cmdStr); + if (!command) + return results; + + command->setup(cli); + results = command->complete(cmdWords.mid(1)).filter(QRegExp("^"+cmdWords.last()+".*")); + } + else + { + QStringList cmdNames = CliCommandFactory::getCommandNames().filter(QRegExp("^"+cmdStr+".*")); + cmdNames.sort(Qt::CaseInsensitive); + foreach (const QString& cmdName, cmdNames) + results << CFG_CLI.Console.CommandPrefixChar.get() + cmdName; + } + + return results; +} + +QStringList CliCompleter::completeQuery(const QString& toBeReplaced, const QString& str, int curPos) +{ + QStringList list; + if (!cli->getCurrentDb()) + return list; + + bool keepOriginalStr = doKeepOriginalStr(str, curPos); + + CompletionHelper completer(str, curPos, cli->getCurrentDb()); + QList expectedTokens = completer.getExpectedTokens().filtered(); + + foreach (const ExpectedTokenPtr& token, expectedTokens) + list << token->value; + + list.removeAll(""); + if (list.size() > 1) + list.removeOne(";"); // we don't want it together with other proposals, cause it introduces problems when proposed by completer + + if (keepOriginalStr) + { + QMutableStringListIterator it(list); + while (it.hasNext()) + it.next().prepend(toBeReplaced); + } + return list; +} + +bool CliCompleter::doKeepOriginalStr(const QString& str, int curPos) +{ + Dialect dialect = Dialect::Sqlite3; + if (cli->getCurrentDb()) + dialect = cli->getCurrentDb()->getDialect(); + + TokenList tokens = Lexer::tokenize(str.mid(0, curPos), dialect); + if (tokens.size() == 0) + return false; + + return tokens.last()->isSeparating(); +} + +char** CliCompleter::toCharArray(const QStringList& list) +{ + if (list.size() == 0) + return nullptr; + + char** array = (char**)malloc((list.size() + 1) * sizeof(char*)); + array[list.size()] = nullptr; + + int i = 0; + foreach (const QString& str, list) +#if defined(Q_OS_WIN) + array[i++] = _strdup(str.toLocal8Bit().data()); +#elif defined(Q_OS_UNIX) + array[i++] = strdup(str.toLocal8Bit().data()); +#endif + + return array; +} diff --git a/SQLiteStudio3/sqlitestudiocli/clicompleter.h b/SQLiteStudio3/sqlitestudiocli/clicompleter.h new file mode 100644 index 0000000..a43a98d --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicompleter.h @@ -0,0 +1,30 @@ +#ifndef CLICOMPLETER_H +#define CLICOMPLETER_H + +#include + +class CLI; + +class CliCompleter +{ + public: + static CliCompleter* getInstance(); + static char** complete(const char* text, int start, int end); + + void init(CLI* value); + + private: + CliCompleter(); + QStringList completeInternal(const QString& toBeReplaced, const QString& text, int curPos); + QStringList completeCommand(const QString& str, int curPos); + QStringList completeQuery(const QString& toBeReplaced, const QString& str, int curPos); + bool doKeepOriginalStr(const QString& str, int curPos); + + static char** toCharArray(const QStringList& list); + + static CliCompleter* instance; + + CLI* cli = nullptr; +}; + +#endif // CLICOMPLETER_H diff --git a/SQLiteStudio3/sqlitestudiocli/climsghandler.cpp b/SQLiteStudio3/sqlitestudiocli/climsghandler.cpp new file mode 100644 index 0000000..d36f9d0 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/climsghandler.cpp @@ -0,0 +1,38 @@ +#include "climsghandler.h" +#include "qio.h" +#include "cli_config.h" +#include "common/unused.h" + +bool cliDebug = false; + +void cliMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + if (!cliDebug) + return; + + UNUSED(context); + + QString txt; + switch (type) { + case QtDebugMsg: + txt = QString("Debug: %1").arg(msg); + break; + case QtWarningMsg: + txt = QString("Warning: %1").arg(msg); + break; + case QtCriticalMsg: + txt = QString("Critical: %1").arg(msg); + break; + case QtFatalMsg: + txt = QString("Fatal: %1").arg(msg); + abort(); + } + + qOut << txt << "\n"; + qOut.flush(); +} + +void setCliDebug(bool enabled) +{ + cliDebug = enabled; +} diff --git a/SQLiteStudio3/sqlitestudiocli/climsghandler.h b/SQLiteStudio3/sqlitestudiocli/climsghandler.h new file mode 100644 index 0000000..e2f184e --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/climsghandler.h @@ -0,0 +1,9 @@ +#ifndef CLIMSGHANDLER_H +#define CLIMSGHANDLER_H + +#include + +void cliMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); +void setCliDebug(bool enabled); + +#endif // CLIMSGHANDLER_H diff --git a/SQLiteStudio3/sqlitestudiocli/cliutils.cpp b/SQLiteStudio3/sqlitestudiocli/cliutils.cpp new file mode 100644 index 0000000..2b5c44d --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cliutils.cpp @@ -0,0 +1,99 @@ +#include "cliutils.h" +#include + +#if defined(Q_OS_WIN32) +#include +#elif defined(Q_OS_UNIX) +#include +#include +#endif + +#if defined(Q_OS_WIN32) + +int getCliColumns() +{ + CONSOLE_SCREEN_BUFFER_INFO data; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &data); + return data.dwSize.X; +} + +int getCliRows() +{ + CONSOLE_SCREEN_BUFFER_INFO data; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &data); + return data.dwSize.Y; +} + +#elif defined(Q_OS_UNIX) + +int getCliColumns() +{ + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + return w.ws_col; +} + +int getCliRows() +{ + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + return w.ws_row; +} + +#endif + +QStringList toAsciiTree(const AsciiTree& tree, const QList& indents, bool topLevel, bool lastNode) +{ + static const QString indentStr = " | "; + static const QString indentStrEmpty = " "; + static const QString branchStr = " +-"; + static const QString branchStrLast = " `-"; + + QStringList lines; + QString line; + + if (!topLevel) + { + // Draw indent before this node + foreach (bool indent, indents) + line += (indent ? indentStr : indentStrEmpty); + + // Draw node prefix + line += (lastNode ? branchStrLast : branchStr); + } + + // Draw label + line += tree.label; + lines << line; + + if (tree.childs.size() == 0) + return lines; + + // Draw childs + int i = 0; + int lastIdx = tree.childs.size() - 1; + QList subIndents = indents; + + if (!topLevel) + subIndents << (lastNode ? false : true); + + foreach (const AsciiTree& subTree, tree.childs) + { + lines += toAsciiTree(subTree, subIndents, false, i == lastIdx); + i++; + } + + return lines; +} + +QString toAsciiTree(const AsciiTree& tree) +{ + QList subIndents; + QStringList lines = toAsciiTree(tree, subIndents, true, true); + return lines.join("\n"); +} + +void initCliUtils() +{ + qRegisterMetaType(); +} diff --git a/SQLiteStudio3/sqlitestudiocli/cliutils.h b/SQLiteStudio3/sqlitestudiocli/cliutils.h new file mode 100644 index 0000000..2130a65 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cliutils.h @@ -0,0 +1,22 @@ +#ifndef CLIUTILS_H +#define CLIUTILS_H + +#include +#include + +void initCliUtils(); + +int getCliColumns(); +int getCliRows(); + +struct AsciiTree +{ + QList childs; + QString label; +}; + +Q_DECLARE_METATYPE(AsciiTree) + +QString toAsciiTree(const AsciiTree& tree); + +#endif // CLIUTILS_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp new file mode 100644 index 0000000..3002ee5 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp @@ -0,0 +1,334 @@ +#include "clicommand.h" +#include "qio.h" +#include "cli_config.h" +#include "cliutils.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "clicommandfactory.h" +#include "services/dbmanager.h" +#include "schemaresolver.h" +#include "cli.h" + +#include + +CliCommand::CliCommand() +{ +} + +CliCommand::~CliCommand() +{ +} + +void CliCommand::setup(CLI *cli) +{ + this->cli = cli; + defineSyntax(); +} + +bool CliCommand::isAsyncExecution() const +{ + return false; +} + +bool CliCommand::parseArgs(const QStringList& args) +{ + bool res = syntax.parse(args); + + if (!res && !syntax.getErrorText().isEmpty()) + { + println(syntax.getErrorText()); + println(); + } + + return res; +} + +QString CliCommand::usage() const +{ + return syntax.getSyntaxDefinition(); +} + +QString CliCommand::usage(const QString& alias) const +{ + return syntax.getSyntaxDefinition(alias); +} + +QString CliCommand::getName() const +{ + return syntax.getName(); +} + +QStringList CliCommand::complete(const QStringList& args) +{ + syntax.parse(args.mid(0, args.size() - 1)); + + QStringList results; + results += syntax.getStrictArgumentCandidates(); + foreach (int id, syntax.getRegularArgumentCandidates()) + results += getCompletionValuesFor(id, args.last()); + + return results; +} + +QStringList CliCommand::aliases() const +{ + return syntax.getAliases(); +} + +void CliCommand::println(const QString &str) +{ + qOut << str << "\n"; + qOut.flush(); +} + +void CliCommand::print(const QString& str) +{ + qOut << str; + qOut.flush(); +} + +void CliCommand::printBox(const QString& str) +{ + int cols = getCliColumns(); + + QStringList lines = str.split("\n"); + println(".---------------------------"); + foreach (const QString& line, lines) + { + foreach (const QString& lineWithMargin, applyMargin(line, cols - 3)) // 2 for "| " and 1 for final new line character + println("| " + lineWithMargin); + } + + println("`---------------------------"); +} + +void CliCommand::printUsage() +{ + println(tr("Usage: %1%2").arg(CFG_CLI.Console.CommandPrefixChar.get()).arg(usage())); + println(""); +} + +QString CliCommand::getFilterAndFixDir(QDir& dir, const QString& path) +{ + if (path.isEmpty()) + return "*"; + + QString filter; + QDir tempDir; + tempDir.setPath(path); + if (tempDir.exists() && path.endsWith("/")) + { + dir.cd(path); + filter = "*"; + } + else if (tempDir.cdUp()) + { + dir.setPath(path); + dir.cdUp(); + filter = QFileInfo(path).fileName() + "*"; + } + else + { + filter = path; + } + return filter; +} + +QStringList CliCommand::getCompletionDbNames() +{ + QStringList results = DBLIST->getDbNames(); + results.sort(Qt::CaseInsensitive); + return results; +} + +QStringList CliCommand::getCompletionTables() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + Dialect dialect = Dialect::Sqlite3; + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(true); + results += wrapObjNamesIfNeeded(resolver.getTables(), dialect); + results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getTables("temp"), dialect)); + foreach (const QString& database, resolver.getDatabases()) + results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getTables(database), dialect)); + + return results; +} + +QStringList CliCommand::getCompletionIndexes() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + Dialect dialect = Dialect::Sqlite3; + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(true); + results += wrapObjNamesIfNeeded(resolver.getIndexes(), dialect); + results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getIndexes("temp"), dialect)); + foreach (const QString& database, resolver.getDatabases()) + results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getIndexes(database), dialect)); + + return results; +} + +QStringList CliCommand::getCompletionTriggers() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + Dialect dialect = Dialect::Sqlite3; + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(true); + results += wrapObjNamesIfNeeded(resolver.getTriggers(), dialect); + results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getTriggers("temp"), dialect)); + foreach (const QString& database, resolver.getDatabases()) + results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getTriggers(database), dialect)); + + return results; +} + +QStringList CliCommand::getCompletionViews() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + Dialect dialect = Dialect::Sqlite3; + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(true); + results += wrapObjNamesIfNeeded(resolver.getViews(), dialect); + results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getViews("temp"), dialect)); + foreach (const QString& database, resolver.getDatabases()) + results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getViews(database), dialect)); + + return results; +} + +QStringList CliCommand::getCompletionDbNamesOrFiles(const QString& partialValue) +{ + QStringList results = getCompletionDbNames(); + results += getCompletionFiles(partialValue); + return results; +} + +QStringList CliCommand::getCompletionFiles(const QString& partialValue) +{ + QDir dir; + QString filter = getFilterAndFixDir(dir, partialValue); + QFileInfoList entries = dir.entryInfoList({filter}, QDir::Files, QDir::LocaleAware|QDir::Name); + + QStringList results; + QString name; + foreach (const QFileInfo& entry, entries) + { + name = entry.fileName(); + if (dir != QDir::current()) + name.prepend(dir.path() + "/"); + + results << name; + } + + return results; +} + +QStringList CliCommand::getCompletionDirs(const QString& partialValue) +{ + QDir dir; + QString filter = getFilterAndFixDir(dir, partialValue); + QFileInfoList entries = dir.entryInfoList({filter}, QDir::Dirs|QDir::NoDotAndDotDot, QDir::LocaleAware|QDir::Name); + + QStringList results; + QString name; + foreach (const QFileInfo& entry, entries) + { + name = entry.fileName(); + if (dir != QDir::current()) + name.prepend(dir.path() + "/"); + + results << name; + } + + return results; +} + +QStringList CliCommand::getCompletionDirsOrFiles(const QString& partialValue) +{ + QStringList results = getCompletionDirs(partialValue); + results += getCompletionFiles(partialValue); + return results; +} + +QStringList CliCommand::getCompletionInternalDbs() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + SchemaResolver resolver(cli->getCurrentDb()); + results += resolver.getDatabases().toList(); + results << "main" << "temp"; + results.sort(Qt::CaseInsensitive); + return results; +} + +QStringList CliCommand::getCompletionValuesFor(int id, const QString& partialValue) +{ + QStringList results; + if (id < 1000) // this base implementation is only for local enum values (>= 1000). + return results; + + switch (static_cast(id)) + { + case CliCommand::DB_NAME: + results += getCompletionDbNames(); + break; + case CliCommand::DB_NAME_OR_FILE: + results += getCompletionDbNamesOrFiles(partialValue); + break; + case CliCommand::FILE_PATH: + results += getCompletionFiles(partialValue); + break; + case CliCommand::DIR_PATH: + results += getCompletionDirs(partialValue); + break; + case CliCommand::CMD_NAME: + results += CliCommandFactory::getCommandNames(); + break; + case CliCommand::DIR_OR_FILE: + results += getCompletionDirsOrFiles(partialValue); + break; + case CliCommand::INTRNAL_DB: + results += getCompletionInternalDbs(); + break; + case CliCommand::TABLE: + results += getCompletionTables(); + break; + case CliCommand::INDEX: + results += getCompletionIndexes(); + break; + case CliCommand::TRIGGER: + results += getCompletionTriggers(); + break; + case CliCommand::VIEW: + results += getCompletionViews(); + break; + case CliCommand::STRING: + break; + } + + return results; +} + +QString CliCommand::cmdName(const QString& cmd) +{ + return CFG_CLI.Console.CommandPrefixChar.get()+cmd; +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h new file mode 100644 index 0000000..4fc8618 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h @@ -0,0 +1,97 @@ +#ifndef CLICOMMAND_H +#define CLICOMMAND_H + +#include "clicommandsyntax.h" +#include +#include +#include +#include + +class QFile; +class SQLiteStudio; +class DbManager; +class CLI; +class Config; + +class CliCommand : public QObject +{ + Q_OBJECT + + public: + CliCommand(); + virtual ~CliCommand(); + + void setup(CLI* cli); + + /** + * @brief execute + */ + virtual void execute() = 0; + + /** + * @brief Short help displayed in commands index. + * @return Single line of command description. + */ + virtual QString shortHelp() const = 0; + + /** + * @brief Full help is displayed when help for specific command was requested. + * @return Multi line, detailed description, including syntax. + */ + virtual QString fullHelp() const = 0; + + virtual bool isAsyncExecution() const; + + virtual void defineSyntax() = 0; + + QStringList aliases() const; + bool parseArgs(const QStringList& args); + QString usage() const; + QString usage(const QString& alias) const; + QString getName() const; + QStringList complete(const QStringList& args); + + protected: + enum ArgIds + { + DB_NAME = 1000, + DB_NAME_OR_FILE = 1001, + FILE_PATH = 1002, + DIR_OR_FILE = 1003, + DIR_PATH = 1004, + CMD_NAME = 1005, + INTRNAL_DB = 1006, + STRING = 1007, + TABLE = 1008, + INDEX = 1009, + TRIGGER = 1010, + VIEW = 1011 + }; + + static void println(const QString& str = ""); + static void print(const QString& str = ""); + static void printBox(const QString& str); + static QString cmdName(const QString& cmd); + + void printUsage(); + QString getFilterAndFixDir(QDir& dir, const QString& path); + QStringList getCompletionDbNames(); + QStringList getCompletionTables(); + QStringList getCompletionIndexes(); + QStringList getCompletionTriggers(); + QStringList getCompletionViews(); + QStringList getCompletionDbNamesOrFiles(const QString& partialValue); + QStringList getCompletionFiles(const QString& partialValue); + QStringList getCompletionDirs(const QString& partialValue); + QStringList getCompletionDirsOrFiles(const QString& partialValue); + QStringList getCompletionInternalDbs(); + virtual QStringList getCompletionValuesFor(int id, const QString& partialValue); + + CLI* cli = nullptr; + CliCommandSyntax syntax; + + signals: + void execComplete(); +}; + +#endif // CLICOMMAND_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp new file mode 100644 index 0000000..e3c14bb --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp @@ -0,0 +1,36 @@ +#include "clicommandadd.h" +#include "cli.h" +#include "services/dbmanager.h" + +void CliCommandAdd::execute() +{ + if (!DBLIST->addDb(syntax.getArgument(DB_NAME), syntax.getArgument(FILE_PATH))) + { + println(tr("Could not add database %1 to list.").arg(syntax.getArgument(FILE_PATH))); + return; + } + + cli->setCurrentDb(DBLIST->getByName(syntax.getArgument(DB_NAME))); + println(tr("Database added: %1").arg(cli->getCurrentDb()->getName())); +} + +QString CliCommandAdd::shortHelp() const +{ + return tr("adds new database to the list"); +} + +QString CliCommandAdd::fullHelp() const +{ + return tr( + "Adds given database pointed by with given to list the databases list. " + "The is just a symbolic name that you can later refer to. Just pick any unique name. " + "For list of databases already on the list use %1 command." + ).arg(cmdName("dblist")); +} + +void CliCommandAdd::defineSyntax() +{ + syntax.setName("add"); + syntax.addArgument(DB_NAME, tr("name", "CLI command syntax")); + syntax.addArgument(FILE_PATH, tr("path", "CLI command syntax")); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h new file mode 100644 index 0000000..ae09ee1 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDADD_H +#define CLICOMMANDADD_H + +#include "clicommand.h" + +class CliCommandAdd : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDADD_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp new file mode 100644 index 0000000..0ca6cb6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp @@ -0,0 +1,34 @@ +#include "clicommandcd.h" +#include +#include + +void CliCommandCd::execute() +{ + QDir dir; + dir.cd(syntax.getArgument(DIR_PATH)); + if (QDir::setCurrent(dir.absolutePath())) + println(tr("Changed directory to: %1").arg(QDir::currentPath())); + else + println(tr("Could not change directory to: %1").arg(QDir::currentPath())); +} + +QString CliCommandCd::shortHelp() const +{ + return tr("changes current workind directory"); +} + +QString CliCommandCd::fullHelp() const +{ + return tr( + "Very similar command to 'cd' known from Unix systems and Windows. " + "It requires a argument to be passed, therefore calling %1 will always cause a change of the directory. " + "To learn what's the current working directory use %2 command and to list contents of the current working directory " + "use %3 command." + ); +} + +void CliCommandCd::defineSyntax() +{ + syntax.setName("cd"); + syntax.addArgument(DIR_PATH, tr("path", "CLI command syntax")); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h new file mode 100644 index 0000000..4cfabc9 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDCD_H +#define CLICOMMANDCD_H + +#include "clicommand.h" + +class CliCommandCd : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDCD_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp new file mode 100644 index 0000000..44fc72c --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp @@ -0,0 +1,51 @@ +#include "clicommandclose.h" +#include "cli.h" +#include "db/db.h" +#include "services/dbmanager.h" + +void CliCommandClose::execute() +{ + if (!syntax.isArgumentSet(DB_NAME) && !cli->getCurrentDb()) + { + println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.") + .arg(cmdName("close")).arg(cmdName("use")).arg(cmdName("close"))); + return; + } + + if (syntax.isArgumentSet(DB_NAME)) + { + Db* db = DBLIST->getByName(syntax.getArgument(DB_NAME)); + if (db) + { + db->close(); + println(tr("Connection to database %1 closed.").arg(db->getName())); + } + else + println(tr("No such database: %1. Use %2 to see list of known databases.").arg(syntax.getArgument(DB_NAME)).arg(cmdName("dblist"))); + } + else if (cli->getCurrentDb()) + { + cli->getCurrentDb()->close(); + println(tr("Connection to database %1 closed.").arg(cli->getCurrentDb()->getName())); + } +} + +QString CliCommandClose::shortHelp() const +{ + return tr("closes given (or current) database"); +} + +QString CliCommandClose::fullHelp() const +{ + return tr( + "Closes database connection. If the database was already closed, nothing happens. " + "If is provided, it should be name of the database to close (as printed by %1 command). " + "The the is not provided, then current working database is closed (see help for %2 for details)." + ).arg(cmdName("dblist")).arg(cmdName("use")); +} + +void CliCommandClose::defineSyntax() +{ + syntax.setName("close"); + syntax.addArgument(DB_NAME, tr("name", "CLI command syntax"), false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h new file mode 100644 index 0000000..04fbbeb --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDCLOSE_H +#define CLICOMMANDCLOSE_H + +#include "clicommand.h" + +class CliCommandClose : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDCLOSE_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp new file mode 100644 index 0000000..cbc16b6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp @@ -0,0 +1,86 @@ +#include "clicommanddblist.h" +#include "cli.h" +#include "services/dbmanager.h" +#include "common/unused.h" +#include "common/utils.h" +#include + +void CliCommandDbList::execute() +{ + if (!cli->getCurrentDb()) + { + println(tr("No current working database defined.")); + return; + } + + QString currentName = cli->getCurrentDb()->getName(); + + println(tr("Databases:")); + QList dbList = DBLIST->getDbList(); + QString path; + QString msg; + + int maxNameLength = tr("Name", "CLI db name column").length(); + int lgt = 0; + foreach (Db* db, dbList) + { + lgt = db->getName().length() + 1; + maxNameLength = qMax(maxNameLength, lgt); + } + + int connStateLength = qMax(tr("Open", "CLI connection state column").length(), tr("Closed", "CLI connection state column").length()); + connStateLength = qMax(connStateLength, tr("Connection", "CLI connection state column").length()); + + msg = pad(tr("Name", "CLI db name column"), maxNameLength, ' '); + msg += "|"; + msg += pad(tr("Connection", "CLI connection state column"), connStateLength, ' '); + msg += "|"; + msg += tr("Database file path"); + println(msg); + + msg = QString("-").repeated(maxNameLength); + msg += "+"; + msg += QString("-").repeated(connStateLength); + msg += "+"; + msg += QString("-").repeated(tr("Database file path").length() + 1); + println(msg); + + QString name; + foreach (Db* db, dbList) + { + bool open = db->isOpen(); + path = db->getPath(); + name = db->getName(); + if (name == currentName) + name.prepend("*"); + else + name.prepend(" "); + + msg = pad(name, maxNameLength, ' '); + msg += "|"; + msg += pad((open ? tr("Open") : tr("Closed")), connStateLength, ' '); + msg += "|"; + msg += path; + println(msg); + } +} + +QString CliCommandDbList::shortHelp() const +{ + return tr("prints list of registered databases"); +} + +QString CliCommandDbList::fullHelp() const +{ + return tr( + "Prints list of databases registered in the SQLiteStudio. Each database on the list can be in open or closed state " + "and .dblist tells you that. The current working database (aka default database) is also marked on the list with '*' at the start of its name. " + "See help for %1 command to learn about the default database." + ).arg(cmdName("use")); +} + +void CliCommandDbList::defineSyntax() +{ + syntax.setName("dblist"); + syntax.addAlias("databases"); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h new file mode 100644 index 0000000..ecbaa6f --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDDBLIST_H +#define CLICOMMANDDBLIST_H + +#include "clicommand.h" + +class CliCommandDbList : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDDBLIST_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp new file mode 100644 index 0000000..4865e23 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp @@ -0,0 +1,26 @@ +#include "clicommanddesc.h" + +CliCommandDesc::CliCommandDesc() +{ +} + +void CliCommandDesc::execute() +{ + +} + +QString CliCommandDesc::shortHelp() const +{ + return tr("shows details about the table"); +} + +QString CliCommandDesc::fullHelp() const +{ + return QString(); +} + +void CliCommandDesc::defineSyntax() +{ + syntax.setName("desc"); + syntax.addArgument(TABLE, tr("table")); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h new file mode 100644 index 0000000..ee52c30 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h @@ -0,0 +1,16 @@ +#ifndef CLICOMMANDDESC_H +#define CLICOMMANDDESC_H + +#include "clicommand.h" + +class CliCommandDesc : public CliCommand +{ + public: + CliCommandDesc(); + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDDESC_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp new file mode 100644 index 0000000..c61f503 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp @@ -0,0 +1,50 @@ +#include "clicommanddir.h" +#include + +void CliCommandDir::execute() +{ + QDir dir; + QFileInfoList entries; + + if (syntax.isArgumentSet(DIR_OR_FILE)) + { + QString filter = getFilterAndFixDir(dir, syntax.getArgument(DIR_OR_FILE)); + entries = dir.entryInfoList({filter}, QDir::AllEntries|QDir::NoDotAndDotDot, QDir::DirsFirst|QDir::LocaleAware|QDir::Name); + } + else + entries = dir.entryInfoList(QDir::AllEntries|QDir::NoDotAndDotDot, QDir::DirsFirst|QDir::LocaleAware|QDir::Name); + + QString name; + foreach (const QFileInfo& entry, entries) + { + name = entry.fileName(); + if (entry.isDir()) + name += "/"; + + if (dir != QDir::current()) + name.prepend(dir.path()+"/"); + + println(name); + } +} + +QString CliCommandDir::shortHelp() const +{ + return tr("lists directories and files in current working directory"); +} + +QString CliCommandDir::fullHelp() const +{ + return tr( + "This is very similar to 'dir' command known from Windows and 'ls' command from Unix systems.\n" + "\n" + "You can pass with wildcard characters to filter output." + ); +} + +void CliCommandDir::defineSyntax() +{ + syntax.setName("dir"); + syntax.addAlias("ls"); + syntax.addArgument(DIR_OR_FILE, tr("pattern"), false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h new file mode 100644 index 0000000..8aa5ccc --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDDIR_H +#define CLICOMMANDDIR_H + +#include "clicommand.h" + +class CliCommandDir : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDDIR_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp new file mode 100644 index 0000000..2c45e1d --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp @@ -0,0 +1,26 @@ +#include "clicommandexit.h" +#include "cli.h" +#include "common/unused.h" + +void CliCommandExit::execute() +{ + cli->exit(); +} + +QString CliCommandExit::shortHelp() const +{ + return tr("quits the application"); +} + +QString CliCommandExit::fullHelp() const +{ + return tr( + "Quits the application. Settings are stored in configuration file and will be restored on next startup." + ); +} + +void CliCommandExit::defineSyntax() +{ + syntax.setName("exit"); + syntax.addAlias("quit"); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h new file mode 100644 index 0000000..dc061bb --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDEXIT_H +#define CLICOMMANDEXIT_H + +#include "clicommand.h" + +class CliCommandExit : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDEXIT_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp new file mode 100644 index 0000000..742709b --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp @@ -0,0 +1,85 @@ +#include "clicommandfactory.h" +#include "cli.h" +#include "clicommandadd.h" +#include "clicommandremove.h" +#include "clicommandexit.h" +#include "clicommanddblist.h" +#include "clicommanduse.h" +#include "clicommandopen.h" +#include "clicommandclose.h" +#include "clicommandsql.h" +#include "clicommandhelp.h" +#include "clicommandtables.h" +#include "clicommandmode.h" +#include "clicommandnullvalue.h" +#include "clicommandhistory.h" +#include "clicommanddir.h" +#include "clicommandpwd.h" +#include "clicommandcd.h" +#include "clicommandtree.h" +#include "clicommanddesc.h" +#include + +QHash CliCommandFactory::mapping; + +#define REGISTER_CMD(Cmd) registerCommand([]() -> CliCommand* {return new Cmd();}) + +void CliCommandFactory::init() +{ + REGISTER_CMD(CliCommandAdd); + REGISTER_CMD(CliCommandRemove); + REGISTER_CMD(CliCommandExit); + REGISTER_CMD(CliCommandDbList); + REGISTER_CMD(CliCommandUse); + REGISTER_CMD(CliCommandOpen); + REGISTER_CMD(CliCommandClose); + REGISTER_CMD(CliCommandSql); + REGISTER_CMD(CliCommandHelp); + REGISTER_CMD(CliCommandTables); + REGISTER_CMD(CliCommandMode); + REGISTER_CMD(CliCommandNullValue); + REGISTER_CMD(CliCommandHistory); + REGISTER_CMD(CliCommandDir); + REGISTER_CMD(CliCommandPwd); + REGISTER_CMD(CliCommandCd); + REGISTER_CMD(CliCommandTree); + REGISTER_CMD(CliCommandDesc); +} + +CliCommand *CliCommandFactory::getCommand(const QString &cmdName) +{ + if (!mapping.contains(cmdName)) + return nullptr; + + return mapping[cmdName](); +} + +QHash CliCommandFactory::getAllCommands() +{ + QHash results; + QHashIterator it(mapping); + while (it.hasNext()) + { + it.next(); + results[it.key()] = it.value()(); + } + + return results; +} + +QStringList CliCommandFactory::getCommandNames() +{ + return mapping.keys(); +} + +void CliCommandFactory::registerCommand(CliCommandCreatorFunc func) +{ + CliCommand* cmd = func(); + cmd->defineSyntax(); + + mapping[cmd->getName()] = func; + foreach (const QString& alias, cmd->aliases()) + mapping[alias] = func; + + delete cmd; +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h new file mode 100644 index 0000000..4329463 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h @@ -0,0 +1,25 @@ +#ifndef CLICOMMANDFACTORY_H +#define CLICOMMANDFACTORY_H + +#include +#include + +class CliCommand; + +class CliCommandFactory +{ + public: + static void init(); + static CliCommand* getCommand(const QString& cmdName); + static QHash getAllCommands(); + static QStringList getCommandNames(); + + private: + typedef CliCommand* (*CliCommandCreatorFunc)(); + + static void registerCommand(CliCommandCreatorFunc func); + + static QHash mapping; +}; + +#endif // CLICOMMANDFACTORY_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp new file mode 100644 index 0000000..7a957c3 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp @@ -0,0 +1,86 @@ +#include "clicommandhelp.h" +#include "clicommandfactory.h" +#include "common/utils.h" +#include "cli_config.h" + +void CliCommandHelp::execute() +{ + if (syntax.isArgumentSet(CMD_NAME)) + printHelp(syntax.getArgument(CMD_NAME)); + else + printHelp(); +} + +QString CliCommandHelp::shortHelp() const +{ + return tr("shows this help message"); +} + +QString CliCommandHelp::fullHelp() const +{ + return tr( + "Use %1 to learn about certain commands supported by the command line interface (CLI) of the SQLiteStudio.\n" + "To see list of supported commands, type %2 without any arguments.\n\n" + "When passing name, you can skip special prefix character ('%3').\n\n" + "You can always execute any command with exactly single '--help' option to see help for that command. " + "It's an alternative for typing: %1 ." + ).arg(cmdName("help")).arg(cmdName("help")).arg(CFG_CLI.Console.CommandPrefixChar.get()).arg(cmdName("help")); +} + +void CliCommandHelp::defineSyntax() +{ + syntax.setName("help"); + syntax.addArgument(CMD_NAME, tr("command", "CLI command syntax"), false); +} + +void CliCommandHelp::printHelp(const QString& cmd) +{ + QString cmdStr = cmd.startsWith(".") ? cmd.mid(1) : cmd; + CliCommand* command = CliCommandFactory::getCommand(cmdStr); + if (!command) + { + println(tr("No such command: %1").arg(cmd)); + println(tr("Type '%1' for list of available commands.").arg(cmdName("help"))); + println(""); + return; + } + command->defineSyntax(); + QStringList aliases = command->aliases(); + QString prefix = CFG_CLI.Console.CommandPrefixChar.get(); + + QString msg; + msg += tr("Usage: %1%2").arg(prefix).arg(command->usage(cmdStr)); + msg += "\n"; + if (aliases.size() > 0) + { + if (aliases.contains(cmdStr)) + { + aliases.removeOne(cmdStr); + aliases << command->getName(); + } + + msg += tr("Aliases: %1").arg(prefix + aliases.join(", " + prefix)); + msg += "\n"; + } + msg += "\n"; + msg += command->fullHelp(); + delete command; + + printBox(msg); +} + +void CliCommandHelp::printHelp() +{ + QHash allCommands = CliCommandFactory::getAllCommands(); + QStringList names = allCommands.keys(); + int width = longest(names).size(); + + names.sort(); + QStringList msgList; + foreach (const QString& cmd, names) + { + msgList << (CFG_CLI.Console.CommandPrefixChar.get() + pad(cmd, width, ' ') + " - " + allCommands[cmd]->shortHelp()); + delete allCommands[cmd]; + } + printBox(msgList.join("\n")); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h new file mode 100644 index 0000000..c12c588 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h @@ -0,0 +1,19 @@ +#ifndef CLICOMMANDHELP_H +#define CLICOMMANDHELP_H + +#include "clicommand.h" + +class CliCommandHelp : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); + + private: + void printHelp(const QString& cmd); + void printHelp(); +}; + +#endif // CLICOMMANDHELP_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp new file mode 100644 index 0000000..fd2a494 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp @@ -0,0 +1,81 @@ +#include "clicommandhistory.h" +#include "cli.h" +#include "common/utils.h" +#include "cliutils.h" +#include "services/config.h" + +void CliCommandHistory::execute() +{ + if (syntax.isOptionSet(OPER_TYPE)) + { + clear(); + return; + } + + if (syntax.isOptionSet(HIST_LIMIT)) + { + setMax(syntax.getOptionValue(HIST_LIMIT)); + return; + } + + if (syntax.isOptionSet(SHOW_LIMIT)) + { + println(tr("Current history limit is set to: %1").arg(CFG_CORE.Console.HistorySize.get())); + return; + } + + int cols = getCliColumns(); + QString hline = pad("", cols, '-'); + foreach (const QString& line, cli->getHistory()) + { + print(hline); + println(line); + } + println(hline); +} + +QString CliCommandHistory::shortHelp() const +{ + return tr("prints history or erases it"); +} + +QString CliCommandHistory::fullHelp() const +{ + return tr( + "When no argument was passed, this command prints command line history. " + "Every history entry is separated with a horizontal line, so multiline entries are easier to read.\n" + "\n" + "When the -c or --clear option is passed, then the history gets erased.\n" + "When the -l or --limit option is passed, it sets the new history entries limit. It requires an additional argument" + "saying how many entries do you want the history to be limited to.\n" + "Use -ql or --querylimit option to see the current limit value." + ); +} + +void CliCommandHistory::defineSyntax() +{ + syntax.setName("history"); + syntax.addOption(OPER_TYPE, "c", "clear"); + syntax.addOptionWithArg(HIST_LIMIT, "l", "limit", tr("number")); + syntax.addOption(SHOW_LIMIT, "ql", "querylimit"); +} + +void CliCommandHistory::clear() +{ + cli->clearHistory(); + println(tr("Console history erased.")); +} + +void CliCommandHistory::setMax(const QString& arg) +{ + bool ok; + int max = arg.toInt(&ok); + if (!ok) + { + println(tr("Invalid number: %1").arg(arg)); + return; + } + CFG_CORE.Console.HistorySize.set(max); + cli->applyHistoryLimit(); + println(tr("History limit set to %1").arg(max)); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h new file mode 100644 index 0000000..f2e07d0 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h @@ -0,0 +1,26 @@ +#ifndef CLICOMMANDHISTORY_H +#define CLICOMMANDHISTORY_H + +#include "clicommand.h" + +class CliCommandHistory : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); + + private: + enum ArgIds + { + OPER_TYPE, + HIST_LIMIT, + SHOW_LIMIT + }; + + void clear(); + void setMax(const QString& arg); +}; + +#endif // CLICOMMANDHISTORY_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp new file mode 100644 index 0000000..b258045 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp @@ -0,0 +1,65 @@ +#include "clicommandmode.h" +#include "common/unused.h" +#include "cli_config.h" + +void CliCommandMode::execute() +{ + if (!syntax.isArgumentSet(MODE)) + { + println(tr("Current results printing mode: %1").arg(CliResultsDisplay::mode(CFG_CLI.Console.ResultsDisplayMode.get()))); + return; + } + + CliResultsDisplay::Mode mode = CliResultsDisplay::mode(syntax.getArgument(MODE).toUpper()); + if (syntax.getArgument(MODE).toUpper() != CliResultsDisplay::mode(mode)) + { + println(tr("Invalid results printing mode: %1").arg(syntax.getArgument(MODE).toUpper())); + return; + } + + CFG_CLI.Console.ResultsDisplayMode.set(mode); + println(tr("New results printing mode: %1").arg(CliResultsDisplay::mode(mode))); +} + +QString CliCommandMode::shortHelp() const +{ + return tr("tells or changes the query results format"); +} + +QString CliCommandMode::fullHelp() const +{ + return tr( + "When called without argument, tells the current output format for a query results. " + "When the is passed, the mode is changed to the given one. " + "Supported modes are:\n" + "- CLASSIC - columns are separated by a comma, not aligned,\n" + "- FIXED - columns have equal and fixed width, they always fit into terminal window width, but the data in columns can be cut off,\n" + "- COLUMNS - like FIXED, but smarter (do not use with huge result sets, see details below),\n" + "- ROW - each column from the row is displayed in new line, so the full data is displayed.\n" + "\n" + "The CLASSIC mode is recommended if you want to see all the data, but you don't want to waste lines for each column. " + "Each row will display full data for every column, but this also means, that columns will not be aligned to each other in next rows. " + "The CLASSIC mode also doesn't respect the width of your terminal (console) window, so if values in columns are wider than the window, " + "the row will be continued in next lines.\n" + "\n" + "The FIXED mode is recommended if you want a readable output and you don't care about long data values. " + "Columns will be aligned, making the output a nice table. The width of columns is calculated from width of the console window " + "and a number of columns.\n" + "\n" + "The COLUMNS mode is similar to FIXED mode, except it tries to be smart and make columns with shorter values more thin, " + "while columns with longer values get more space. First to shrink are columns with longest headers (so the header names are to be " + "cut off as first), then columns with the longest values are shrinked, up to the moment when all columns fit into terminal window.\n" + "ATTENTION! The COLUMNS mode reads all the results from the query at once in order to evaluate column widhts, therefore it is dangerous " + "to use this mode when working with huge result sets. Keep in mind that this mode will load entire result set into memory.\n" + "\n" + "The ROW mode is recommended if you need to see whole values and you don't expect many rows to be displayed, because this mode " + "displays a line of output per each column, so you'll get 10 lines for single row with 10 columns, then if you have 10 of such rows, " + "you will get 100 lines of output (+1 extra line per each row, to separate rows from each other)." + ); +} + +void CliCommandMode::defineSyntax() +{ + syntax.setName("mode"); + syntax.addStrictArgument(MODE, {"classic", "fixed", "columns", "row"}, false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h new file mode 100644 index 0000000..2b087d3 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h @@ -0,0 +1,21 @@ +#ifndef CLICOMMANDMODE_H +#define CLICOMMANDMODE_H + +#include "clicommand.h" + +class CliCommandMode : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); + + private: + enum ArgIgs + { + MODE + }; +}; + +#endif // CLICOMMANDMODE_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp new file mode 100644 index 0000000..9616d9e --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp @@ -0,0 +1,32 @@ +#include "clicommandnullvalue.h" +#include "cli_config.h" + +void CliCommandNullValue::execute() +{ + if (syntax.isArgumentSet(STRING)) + CFG_CLI.Console.NullValue.set(syntax.getArgument(STRING)); + + println(tr("Current NULL representation string: %1").arg(CFG_CLI.Console.NullValue.get())); + return; +} + +QString CliCommandNullValue::shortHelp() const +{ + return tr("tells or changes the NULL representation string"); +} + +QString CliCommandNullValue::fullHelp() const +{ + return tr( + "If no argument was passed, it tells what's the current NULL value representation " + "(that is - what is printed in place of NULL values in query results). " + "If the argument is given, then it's used as a new string to be used for NULL representation." + ); +} + +void CliCommandNullValue::defineSyntax() +{ + syntax.setName("null"); + syntax.addAlias("nullvalue"); + syntax.addArgument(STRING, QObject::tr("string", "CLI command syntax"), false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h new file mode 100644 index 0000000..4ac4699 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDNULLVALUE_H +#define CLICOMMANDNULLVALUE_H + +#include "clicommand.h" + +class CliCommandNullValue : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDNULLVALUE_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp new file mode 100644 index 0000000..fef1737 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp @@ -0,0 +1,84 @@ +#include "clicommandopen.h" +#include "cli.h" +#include "services/dbmanager.h" +#include +#include +#include + +void CliCommandOpen::execute() +{ + if (!syntax.isArgumentSet(DB_NAME_OR_FILE) && !cli->getCurrentDb()) + { + println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.") + .arg(cmdName("open")).arg(cmdName("use")).arg(cmdName("open"))); + return; + } + + Db* db = nullptr; + if (syntax.isArgumentSet(DB_NAME_OR_FILE)) + { + QString arg = syntax.getArgument(DB_NAME_OR_FILE); + db = DBLIST->getByName(arg); + if (!db) + { + if (QFile::exists(arg)) + { + QString newName = DbManager::generateDbName(arg); + if (!DBLIST->addDb(newName, arg, false)) + { + println(tr("Could not add database %1 to list.").arg(arg)); + return; + } + db = DBLIST->getByName(arg); + Q_ASSERT(db != nullptr); + } + else + { + println(tr("File %1 doesn't exist in %2. Cannot open inexisting database with %3 command. " + "To create a new database, use %4 command.").arg(arg).arg(QDir::currentPath()) + .arg(cmdName("open")).arg(cmdName("add"))); + return; + } + } + } + else + { + db = cli->getCurrentDb(); + if (!db) + { + qCritical() << "Default database is not in the list!"; + return ; + } + } + + if (!db->open()) + { + println(db->getErrorText()); + return; + } + + cli->setCurrentDb(db); + println(tr("Database %1 has been open and set as the current working database.").arg(db->getName())); +} + +QString CliCommandOpen::shortHelp() const +{ + return tr("opens database connection"); +} + +QString CliCommandOpen::fullHelp() const +{ + return tr( + "Opens connection to the database. If no additional argument was passed, then the connection is open to the " + "current default database (see help for %1 for details). However if an argument was passed, it can be either " + " of the registered database to open, or it can be to the database file to open. " + "In the second case, the gets registered on the list with a generated name, but only for the period " + "of current application session. After restarting application such database is not restored on the list." + ).arg(cmdName("use")); +} + +void CliCommandOpen::defineSyntax() +{ + syntax.setName("open"); + syntax.addAlternatedArgument(DB_NAME_OR_FILE, {tr("name", "CLI command syntax"), tr("path", "CLI command syntax")}, false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h new file mode 100644 index 0000000..e66f574 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDOPEN_H +#define CLICOMMANDOPEN_H + +#include "clicommand.h" + +class CliCommandOpen : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDOPEN_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp new file mode 100644 index 0000000..f96cae4 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp @@ -0,0 +1,28 @@ +#include "clicommandpwd.h" +#include "common/unused.h" +#include + +void CliCommandPwd::execute() +{ + QDir dir; + println(dir.absolutePath()); +} + +QString CliCommandPwd::shortHelp() const +{ + return tr("prints the current working directory"); +} + +QString CliCommandPwd::fullHelp() const +{ + return tr( + "This is the same as 'pwd' command on Unix systems and 'cd' command without arguments on Windows. " + "It prints current working directory. You can change the current working directory with %1 command " + "and you can also list contents of the current working directory with %2 command." + ).arg(cmdName("cd")).arg(cmdName("dir")); +} + +void CliCommandPwd::defineSyntax() +{ + syntax.setName("pwd"); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h new file mode 100644 index 0000000..3568ce1 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDPWD_H +#define CLICOMMANDPWD_H + +#include "clicommand.h" + +class CliCommandPwd : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDPWD_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp new file mode 100644 index 0000000..6474a78 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp @@ -0,0 +1,51 @@ +#include "clicommandremove.h" +#include "cli.h" +#include "services/dbmanager.h" +#include "db/db.h" + +void CliCommandRemove::execute() +{ + QString dbName = syntax.getArgument(DB_NAME); + Db* db = DBLIST->getByName(dbName); + if (!db) + { + println(tr("No such database: %1").arg(dbName)); + return; + } + + bool isCurrent = cli->getCurrentDb() == db; + QString name = db->getName(); + + DBLIST->removeDb(db); + println(tr("Database removed: %1").arg(name)); + + QList dblist = DBLIST->getDbList(); + if (isCurrent && dblist.size() > 0) + { + cli->setCurrentDb(dblist[0]); + println(tr("New current database set:")); + println(cli->getCurrentDb()->getName()); + } + else + cli->setCurrentDb(nullptr); +} + +QString CliCommandRemove::shortHelp() const +{ + return tr("removes database from the list"); +} + +QString CliCommandRemove::fullHelp() const +{ + return tr( + "Removes database from the list of registered databases. " + "If the database was not on the list (see %1 command), then error message is printed " + "and nothing more happens." + ).arg(cmdName("dblist")); +} + +void CliCommandRemove::defineSyntax() +{ + syntax.setName("remove"); + syntax.addArgument(DB_NAME, tr("name", "CLI command syntax")); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h new file mode 100644 index 0000000..f4b5402 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDREMOVE_H +#define CLICOMMANDREMOVE_H + +#include "clicommand.h" + +class CliCommandRemove : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDREMOVE_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp new file mode 100644 index 0000000..3f2c4b3 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp @@ -0,0 +1,506 @@ +#include "clicommandsql.h" +#include "cli.h" +#include "parser/ast/sqliteselect.h" +#include "parser/parser.h" +#include "parser/parsererror.h" +#include "db/queryexecutor.h" +#include "qio.h" +#include "common/unused.h" +#include "cli_config.h" +#include "cliutils.h" +#include +#include + +void CliCommandSql::execute() +{ + if (!cli->getCurrentDb()) + { + println(tr("No working database is set.\n" + "Call %1 command to set working database.\n" + "Call %2 to see list of all databases.") + .arg(cmdName("use")).arg(cmdName("dblist"))); + + return; + } + + Db* db = cli->getCurrentDb(); + if (!db || !db->isOpen()) + { + println(tr("Database is not open.")); + return; + } + + // Executor deletes itself later when called with lambda. + QueryExecutor *executor = new QueryExecutor(db, syntax.getArgument(STRING)); + connect(executor, SIGNAL(executionFinished(SqlQueryPtr)), this, SIGNAL(execComplete())); + connect(executor, SIGNAL(executionFailed(int,QString)), this, SLOT(executionFailed(int,QString))); + connect(executor, SIGNAL(executionFailed(int,QString)), this, SIGNAL(execComplete())); + + executor->exec([=](SqlQueryPtr results) + { + if (results->isError()) + return; // should not happen, since results handler function is called only for successful executions + + switch (CFG_CLI.Console.ResultsDisplayMode.get()) + { + case CliResultsDisplay::FIXED: + printResultsFixed(executor, results); + break; + case CliResultsDisplay::COLUMNS: + printResultsColumns(executor, results); + break; + case CliResultsDisplay::ROW: + printResultsRowByRow(executor, results); + break; + default: + printResultsClassic(executor, results); + break; + } + }); +} + +QString CliCommandSql::shortHelp() const +{ + return tr("executes SQL query"); +} + +QString CliCommandSql::fullHelp() const +{ + return tr( + "This command is executed every time you enter SQL query in command prompt. " + "It executes the query on the current working database (see help for %1 for details). " + "There's no sense in executing this command explicitly. Instead just type the SQL query " + "in the command prompt, without any command prefixed." + ).arg(cmdName("use")); +} + +bool CliCommandSql::isAsyncExecution() const +{ + return true; +} + +void CliCommandSql::defineSyntax() +{ + syntax.setName("query"); + syntax.addArgument(STRING, tr("sql", "CLI command syntax")); + syntax.setStrictArgumentCount(false); +} + +void CliCommandSql::printResultsClassic(QueryExecutor* executor, SqlQueryPtr results) +{ + int metaColumns = executor->getMetaColumnCount(); + int resultColumnCount = executor->getResultColumns().size(); + + // Columns + foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns()) + qOut << resCol->displayName << "|"; + + qOut << "\n"; + + // Data + SqlResultsRowPtr row; + QList values; + int i; + while (results->hasNext()) + { + row = results->next(); + i = 0; + values = row->valueList().mid(metaColumns); + foreach (QVariant value, values) + { + qOut << getValueString(value); + if ((i + 1) < resultColumnCount) + qOut << "|"; + + i++; + } + + qOut << "\n"; + } + qOut.flush(); +} + +void CliCommandSql::printResultsFixed(QueryExecutor* executor, SqlQueryPtr results) +{ + QList resultColumns = executor->getResultColumns(); + int resultColumnsCount = resultColumns.size(); + int metaColumns = executor->getMetaColumnCount(); + int termCols = getCliColumns(); + int baseColWidth = termCols / resultColumns.size() - 1; + + if (resultColumnsCount == 0) + return; + + if ((resultColumnsCount * 2 - 1) > termCols) + { + println(tr("Too many columns to display in %1 mode.").arg("FIXED")); + return; + } + + int width; + QList widths; + for (int i = 0; i < resultColumnsCount; i++) + { + width = baseColWidth; + if (i+1 == resultColumnsCount) + width += (termCols - resultColumnsCount * (baseColWidth + 1) + 1); + + widths << width; + } + + // Columns + QStringList columns; + foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns()) + columns << resCol->displayName; + + printColumnHeader(widths, columns); + + // Data + while (results->hasNext()) + printColumnDataRow(widths, results->next(), metaColumns); + + qOut.flush(); +} + +void CliCommandSql::printResultsColumns(QueryExecutor* executor, SqlQueryPtr results) +{ + // Check if we don't have more columns than we can display + QList resultColumns = executor->getResultColumns(); + int termCols = getCliColumns(); + int resultColumnsCount = resultColumns.size(); + QStringList headerNames; + if (resultColumnsCount == 0) + return; + + // Every column requires at least 1 character width + column separators between them + if ((resultColumnsCount * 2 - 1) > termCols) + { + println(tr("Too many columns to display in %1 mode.").arg("COLUMNS")); + return; + } + + // Preload data (we will calculate column widths basing on real values) + QList allRows = results->getAll(); + int metaColumns = executor->getMetaColumnCount(); + + // Get widths of each column in every data row, remember the longest ones + QList columnWidths; + SortedColumnWidth* colWidth = nullptr; + foreach (const QueryExecutor::ResultColumnPtr& resCol, resultColumns) + { + colWidth = new SortedColumnWidth(); + colWidth->setHeaderWidth(resCol->displayName.length()); + columnWidths << colWidth; + headerNames << resCol->displayName; + } + + int dataLength; + foreach (const SqlResultsRowPtr& row, allRows) + { + for (int i = 0; i < resultColumnsCount; i++) + { + dataLength = row->value(metaColumns + i).toString().length(); + columnWidths[i]->setMinDataWidth(dataLength); + } + } + + // Calculate width as it would be required to display entire rows + int totalWidth = 0; + foreach (colWidth, columnWidths) + totalWidth += colWidth->getWidth(); + + totalWidth += (resultColumnsCount - 1); // column separators + + // Adjust column sizes to fit into terminal window + if (totalWidth < termCols) + { + // Expanding last column + int diff = termCols - totalWidth; + columnWidths.last()->incrWidth(diff); + } + else if (totalWidth > termCols) + { + // Shrinking columns + shrinkColumns(columnWidths, termCols, resultColumnsCount, totalWidth); + } + + // Printing + QList finalWidths; + foreach (colWidth, columnWidths) + finalWidths << colWidth->getWidth(); + + printColumnHeader(finalWidths, headerNames); + + foreach (SqlResultsRowPtr row, allRows) + printColumnDataRow(finalWidths, row, metaColumns); + + qOut.flush(); +} + +void CliCommandSql::printResultsRowByRow(QueryExecutor* executor, SqlQueryPtr results) +{ + // Columns + int metaColumns = executor->getMetaColumnCount(); + int colWidth = 0; + foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns()) + { + if (resCol->displayName.length() > colWidth) + colWidth = resCol->displayName.length(); + } + + QStringList columns; + foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns()) + columns << pad(resCol->displayName, -colWidth, ' '); + + // Data + static const QString rowCntTemplate = tr("Row %1"); + int termWidth = getCliColumns(); + QString rowCntString; + int i; + int rowCnt = 1; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + i = 0; + rowCntString = " " + rowCntTemplate.arg(rowCnt) + " "; + qOut << center(rowCntString, termWidth - 1, '-') << "\n"; + foreach (QVariant value, row->valueList().mid(metaColumns)) + { + qOut << columns[i] + ": " + getValueString(value) << "\n"; + i++; + } + rowCnt++; + } + qOut.flush(); +} + +void CliCommandSql::shrinkColumns(QList& columnWidths, int termCols, int resultColumnsCount, int totalWidth) +{ + // This implements quite a smart shrinking algorithm: + // All columns are sorted by their current total width (data and header width) + // and then longest headers are shrinked first, then if headers are no longer a problem, + // but the data is - then longest data values are shrinked. + // If either the hader or the data value is huge (way more than fits into terminal), + // then such column is shrinked in one step to a reasonable width, so it can be later + // shrinked more precisely. + int maxSingleColumnWidth = (termCols - (resultColumnsCount - 1) * 2 ); + bool shrinkData; + int previousTotalWidth = -1; + while (totalWidth > termCols && totalWidth != previousTotalWidth) + { + shrinkData = true; + previousTotalWidth = totalWidth; + + // Sort columns by current widths + qSort(columnWidths); + + // See if we can shrink headers only, or we already need to shrink the data + foreach (SortedColumnWidth* colWidth, columnWidths) + { + if (colWidth->isHeaderLonger()) + { + shrinkData = false; + break; + } + } + + // Do the shrinking + if (shrinkData) + { + for (int i = resultColumnsCount - 1; i >= 0; i--) + { + // If the data is way larger then the terminal, shrink it to reasonable length in one step. + // We also make sure that after data shrinking, the header didn't become longer than the data, + // cause at this moment, we were finished with headers and we enforce shrinking data + // and so do with headers. + if (columnWidths[i]->getDataWidth() > maxSingleColumnWidth) + { + totalWidth -= (columnWidths[i]->getDataWidth() - maxSingleColumnWidth); + columnWidths[i]->setDataWidth(maxSingleColumnWidth); + columnWidths[i]->setMaxHeaderWidth(maxSingleColumnWidth); + break; + } + else if (columnWidths[i]->getDataWidth() > 1) // just shrink it by 1 + { + totalWidth -= 1; + columnWidths[i]->decrDataWidth(); + columnWidths[i]->setMaxHeaderWidth(columnWidths[i]->getDataWidth()); + break; + } + } + } + else // shrinking headers + { + for (int i = resultColumnsCount - 1; i >= 0; i--) + { + // We will shrink only the header that + if (!columnWidths[i]->isHeaderLonger()) + continue; + + // If the header is way larger then the terminal, shrink it to reasonable length in one step + if (columnWidths[i]->getHeaderWidth() > maxSingleColumnWidth) + { + totalWidth -= (columnWidths[i]->getHeaderWidth() - maxSingleColumnWidth); + columnWidths[i]->setHeaderWidth(maxSingleColumnWidth); + break; + } + else if (columnWidths[i]->getHeaderWidth() > 1) // otherwise just shrink it by 1 + { + totalWidth -= 1; + columnWidths[i]->decrHeaderWidth(); + break; + } + } + } + } + + if (totalWidth == previousTotalWidth && totalWidth > termCols) + qWarning() << "The shrinking algorithm in printResultsColumns() failed, it could not shrink columns enough."; +} + +void CliCommandSql::printColumnHeader(const QList& widths, const QStringList& columns) +{ + QStringList line; + int i = 0; + foreach (const QString& col, columns) + { + line << pad(col.left(widths[i]), widths[i], ' '); + i++; + } + + qOut << line.join("|"); + + line.clear(); + QString hline("-"); + for (i = 0; i < columns.count(); i++) + line << hline.repeated(widths[i]); + + qOut << line.join("+"); +} + +void CliCommandSql::printColumnDataRow(const QList& widths, const SqlResultsRowPtr& row, int rowIdOffset) +{ + int i = 0; + QStringList line; + foreach (const QVariant& value, row->valueList().mid(rowIdOffset)) + { + line << pad(getValueString(value).left(widths[i]), widths[i], ' '); + i++; + } + + qOut << line.join("|"); +} + +QString CliCommandSql::getValueString(const QVariant& value) +{ + if (value.isValid() && !value.isNull()) + return value.toString(); + + return CFG_CLI.Console.NullValue.get(); +} + +void CliCommandSql::executionFailed(int code, const QString& msg) +{ + UNUSED(code); + qOut << tr("Query execution error: %1").arg(msg) << "\n\n"; + qOut.flush(); +} + +CliCommandSql::SortedColumnWidth::SortedColumnWidth() +{ + dataWidth = 0; + headerWidth = 0; + width = 0; +} + +bool CliCommandSql::SortedColumnWidth::operator<(const CliCommandSql::SortedColumnWidth& other) +{ + return width < other.width; +} + +int CliCommandSql::SortedColumnWidth::getHeaderWidth() const +{ + return headerWidth; +} + +void CliCommandSql::SortedColumnWidth::setHeaderWidth(int value) +{ + headerWidth = value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::setMaxHeaderWidth(int value) +{ + if (headerWidth > value) + { + headerWidth = value; + updateWidth(); + } +} + +void CliCommandSql::SortedColumnWidth::incrHeaderWidth(int value) +{ + headerWidth += value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::decrHeaderWidth(int value) +{ + headerWidth -= value; + updateWidth(); +} + +int CliCommandSql::SortedColumnWidth::getDataWidth() const +{ + return dataWidth; +} + +void CliCommandSql::SortedColumnWidth::setDataWidth(int value) +{ + dataWidth = value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::setMinDataWidth(int value) +{ + if (dataWidth < value) + { + dataWidth = value; + updateWidth(); + } +} + +void CliCommandSql::SortedColumnWidth::incrDataWidth(int value) +{ + dataWidth += value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::decrDataWidth(int value) +{ + dataWidth -= value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::incrWidth(int value) +{ + width += value; + dataWidth = width; + headerWidth = width; +} + +int CliCommandSql::SortedColumnWidth::getWidth() const +{ + return width; +} + +bool CliCommandSql::SortedColumnWidth::isHeaderLonger() const +{ + return headerWidth > dataWidth; +} + +void CliCommandSql::SortedColumnWidth::updateWidth() +{ + width = qMax(headerWidth, dataWidth); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h new file mode 100644 index 0000000..5423a85 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h @@ -0,0 +1,66 @@ +#ifndef CLICOMMANDSQL_H +#define CLICOMMANDSQL_H + +#include "clicommand.h" +#include "db/sqlquery.h" + +class QueryExecutor; + +class CliCommandSql : public CliCommand +{ + Q_OBJECT + + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + bool isAsyncExecution() const; + void defineSyntax(); + + private: + class SortedColumnWidth + { + public: + SortedColumnWidth(); + + bool operator<(const SortedColumnWidth& other); + + int getHeaderWidth() const; + void setHeaderWidth(int value); + void setMaxHeaderWidth(int value); + void incrHeaderWidth(int value = 1); + void decrHeaderWidth(int value = 1); + + int getDataWidth() const; + void setDataWidth(int value); + void setMinDataWidth(int value); + void incrDataWidth(int value = 1); + void decrDataWidth(int value = 1); + + void incrWidth(int value = 0); + int getWidth() const; + bool isHeaderLonger() const; + + private: + void updateWidth(); + + int width; + int headerWidth; + int dataWidth; + }; + + void printResultsClassic(QueryExecutor *executor, SqlQueryPtr results); + void printResultsFixed(QueryExecutor *executor, SqlQueryPtr results); + void printResultsColumns(QueryExecutor *executor, SqlQueryPtr results); + void printResultsRowByRow(QueryExecutor *executor, SqlQueryPtr results); + void shrinkColumns(QList& columnWidths, int termCols, int resultColumnsCount, int totalWidth); + void printColumnHeader(const QList& widths, const QStringList& columns); + void printColumnDataRow(const QList& widths, const SqlResultsRowPtr& row, int rowIdOffset); + + QString getValueString(const QVariant& value); + + private slots: + void executionFailed(int code, const QString& msg); +}; + +#endif // CLICOMMANDSQL_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp new file mode 100644 index 0000000..9a6b325 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp @@ -0,0 +1,79 @@ +#include "clicommandtables.h" +#include "cli.h" +#include "schemaresolver.h" +#include "services/dbmanager.h" +#include "common/utils.h" + +void CliCommandTables::execute() +{ + Db* db = nullptr; + if (syntax.isArgumentSet(DB_NAME)) + { + db = DBLIST->getByName(syntax.getArgument(DB_NAME)); + if (!db) + { + println(tr("No such database: %1. Use .dblist to see list of known databases.").arg(syntax.getArgument(DB_NAME))); + return; + } + } + else if (cli->getCurrentDb()) + { + db = cli->getCurrentDb(); + } + else + { + println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.") + .arg(cmdName("tables")).arg(cmdName("use")).arg(cmdName("tables"))); + return; + } + + if (!db->isOpen()) + { + println(tr("Database %1 is closed.").arg(db->getName())); + return; + } + + println(); + + SchemaResolver resolver(db); + resolver.setIgnoreSystemObjects(!syntax.isOptionSet(SHOW_SYSTEM_TABLES)); + QSet dbList; + dbList << "main" << "temp"; + dbList += resolver.getDatabases(); + + int width = longest(dbList.toList()).length(); + width = qMax(width, tr("Database").length()); + + println(pad(tr("Database"), width, ' ') + " " + tr("Table")); + println(pad("", width, '-') + "-------------------"); + + foreach (const QString& dbName, dbList) + { + foreach (const QString& table, resolver.getTables(dbName)) + println(pad(dbName, width, ' ') + " " + table); + } + + println(); +} + +QString CliCommandTables::shortHelp() const +{ + return tr("prints list of tables in the database"); +} + +QString CliCommandTables::fullHelp() const +{ + return tr( + "Prints list of tables in given or in the current working database. " + "Note, that the should be the name of the registered database (see %1). " + "The output list includes all tables from any other databases attached to the queried database.\n" + "When the -s option is given, then system tables are also listed." + ).arg(cmdName("use")); +} + +void CliCommandTables::defineSyntax() +{ + syntax.setName("tables"); + syntax.addArgument(DB_NAME, tr("database", "CLI command syntax"), false); + syntax.addOptionShort(SHOW_SYSTEM_TABLES, "s"); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h new file mode 100644 index 0000000..68ae314 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h @@ -0,0 +1,21 @@ +#ifndef CLICOMMANDTABLES_H +#define CLICOMMANDTABLES_H + +#include "clicommand.h" + +class CliCommandTables : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); + + private: + enum ArgIds + { + SHOW_SYSTEM_TABLES + }; +}; + +#endif // CLICOMMANDTABLES_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp new file mode 100644 index 0000000..cbd57c6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp @@ -0,0 +1,154 @@ +#include "clicommandtree.h" +#include "cli.h" +#include "common/sortedhash.h" +#include "common/unused.h" + +const QString CliCommandTree::metaNodeNameTemplate = "<%1>"; + +void CliCommandTree::execute() +{ + if (!cli->getCurrentDb()) + { + println(tr("No current working database is selected. Use %1 to define one and then run %2.").arg(cmdName("use")).arg(cmdName("tree"))); + return; + } + + bool printColumns = syntax.isOptionSet(COLUMNS); + bool printSystemObjects = syntax.isOptionSet(SYSTEM_OBJECTS); + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(!printSystemObjects); + + QStringList databases; + if (syntax.isArgumentSet(INTRNAL_DB)) + { + databases << syntax.getArgument(INTRNAL_DB); + } + else + { + databases << "main" << "temp"; + databases += resolver.getDatabases().toList(); + } + + AsciiTree tree; + tree.label = cli->getCurrentDb()->getName(); + foreach (const QString& database, databases) + { + tree.childs << getDatabaseTree(database, resolver, printColumns); + } + + println(); + println(toAsciiTree(tree)); + println(); +} + +AsciiTree CliCommandTree::getDatabaseTree(const QString& database, SchemaResolver& resolver, bool printColumns) +{ + QStringList tables = resolver.getTables(database); + QStringList views = resolver.getViews(database); + + AsciiTree tree; + AsciiTree tablesTree; + AsciiTree viewsTree; + + tablesTree.label = metaNodeNameTemplate.arg(tr("Tables")); + foreach (const QString& table, tables) + tablesTree.childs << getTableTree(database, table, resolver, printColumns); + + viewsTree.label = metaNodeNameTemplate.arg(tr("Views")); + foreach (const QString& view, views) + viewsTree.childs << getViewTree(database, view, resolver); + + tree.label = database; + tree.childs << tablesTree << viewsTree; + return tree; +} + +AsciiTree CliCommandTree::getTableTree(const QString& database, const QString& table, SchemaResolver& resolver, bool printColumns) +{ + QStringList columns; + if (printColumns) + columns = resolver.getTableColumns(database, table); + + QStringList indexes = resolver.getIndexesForTable(database, table); + QStringList triggers = resolver.getTriggersForTable(database, table); + + AsciiTree tree; + AsciiTree columnsTree; + AsciiTree indexesTree; + AsciiTree triggersTree; + + if (printColumns) + { + columnsTree.label = metaNodeNameTemplate.arg(tr("Columns")); + foreach (const QString& column, columns) + columnsTree.childs << getTreeLeaf(column); + } + + indexesTree.label = metaNodeNameTemplate.arg(tr("Indexes")); + foreach (const QString& index, indexes) + indexesTree.childs << getTreeLeaf(index); + + triggersTree.label = metaNodeNameTemplate.arg(tr("Triggers")); + foreach (const QString& trig, triggers) + triggersTree.childs << getTreeLeaf(trig); + + if (printColumns) + tree.childs << columnsTree; + + tree.label = table; + tree.childs << indexesTree; + tree.childs << triggersTree; + + return tree; +} + +AsciiTree CliCommandTree::getViewTree(const QString& database, const QString& view, SchemaResolver& resolver) +{ + QStringList triggers = resolver.getTriggersForView(database, view); + + AsciiTree tree; + AsciiTree triggersTree; + + triggersTree.label = metaNodeNameTemplate.arg(tr("Triggers")); + foreach (const QString& trig, triggers) + triggersTree.childs << getTreeLeaf(trig); + + tree.label = view; + tree.childs << triggersTree; + + return tree; +} + +AsciiTree CliCommandTree::getTreeLeaf(const QString& column) +{ + AsciiTree tree; + tree.label = column; + return tree; +} + +QString CliCommandTree::shortHelp() const +{ + return tr("prints all objects in the database as a tree"); +} + +QString CliCommandTree::fullHelp() const +{ + return tr( + "Prints all objects (tables, indexes, triggers and views) that are in the database as a tree. " + "The tree is very similar to the one that you can see in GUI client of the SQLiteStudio.\n" + "When -c option is given, then also columns will be listed under each table.\n" + "When -s option is given, then also system objects will be printed (sqlite_* tables, autoincrement indexes, etc).\n" + "The database argument is optional and if provided, then only given database will be printed. This is not a registered database name, " + "but instead it's an internal SQLite database name, like 'main', 'temp', or any attached database name. To print tree for other " + "registered database, call %1 first to switch the working database, and then use %2 command." + ).arg(cmdName("use")).arg(cmdName("tree")); +} + +void CliCommandTree::defineSyntax() +{ + syntax.setName("tree"); + syntax.addOptionShort(COLUMNS, "c"); + syntax.addOptionShort(SYSTEM_OBJECTS, "s"); + syntax.addArgument(INTRNAL_DB, "database", false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h new file mode 100644 index 0000000..815d781 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h @@ -0,0 +1,31 @@ +#ifndef CLICOMMANDTREE_H +#define CLICOMMANDTREE_H + +#include "schemaresolver.h" +#include "cliutils.h" +#include "clicommand.h" + +class CliCommandTree : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); + + private: + enum Opts + { + COLUMNS, + SYSTEM_OBJECTS + }; + + AsciiTree getDatabaseTree(const QString& database, SchemaResolver& resolver, bool printColumns); + AsciiTree getTableTree(const QString& database, const QString& table, SchemaResolver& resolver, bool printColumns); + AsciiTree getViewTree(const QString& database, const QString& view, SchemaResolver& resolver); + AsciiTree getTreeLeaf(const QString& column); + + static const QString metaNodeNameTemplate; +}; + +#endif // CLICOMMANDTREE_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp new file mode 100644 index 0000000..ef0f641 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp @@ -0,0 +1,64 @@ +#include "clicommanduse.h" +#include "cli.h" +#include "services/config.h" +#include "../cli_config.h" +#include "services/dbmanager.h" + +void CliCommandUse::execute() +{ + if (syntax.isArgumentSet(DB_NAME)) + { + if (!cli->getCurrentDb()) + { + println(tr("No current database selected.")); + return; + } + println(tr("Current database: %1").arg(cli->getCurrentDb()->getName())); + return; + } + + Db* db = DBLIST->getByName(syntax.getArgument(DB_NAME)); + if (!db) + { + println(tr("No such database: %1").arg(syntax.getArgument(DB_NAME))); + return; + } + + cli->setCurrentDb(db); + CFG_CLI.Console.DefaultDatabase.set(db->getName()); + + println(tr("Current database: %1").arg(db->getName())); +} + +QString CliCommandUse::shortHelp() const +{ + return tr("changes default working database"); +} + +QString CliCommandUse::fullHelp() const +{ + return tr( + "Changes current working database to . If the database is not registered in the application, " + "then the error message is printed and no change is made.\n" + "\n" + "What is current working database?\n" + "When you type a SQL query to be executed, it is executed on the default database, which is also known as " + "the current working database. Most of database-related commands can also work using default database, if no database was " + "provided in their arguments. The current database is always identified by command line prompt. " + "The default database is always defined (unless there is no database on the list at all).\n" + "\n" + "The default database can be selected in various ways:\n" + "- using %1 command,\n" + "- by passing database file name to the application startup parameters,\n" + "- by passing registered database name to the application startup parameters,\n" + "- by restoring previously selected default database from saved configuration,\n" + "- or when default database was not selected by any of the above, then first database from the registered databases list " + "becomes the default one." + ).arg(cmdName("use")); +} + +void CliCommandUse::defineSyntax() +{ + syntax.setName("use"); + syntax.addArgument(DB_NAME, tr("name", "CLI command syntax"), false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h new file mode 100644 index 0000000..9a8f280 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDUSE_H +#define CLICOMMANDUSE_H + +#include "clicommand.h" + +class CliCommandUse : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDUSE_H diff --git a/SQLiteStudio3/sqlitestudiocli/main.cpp b/SQLiteStudio3/sqlitestudiocli/main.cpp new file mode 100644 index 0000000..118e3f6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/main.cpp @@ -0,0 +1,92 @@ +#include "cli.h" +#include "clicommandexecutor.h" +#include "sqlitestudio.h" +#include "commands/clicommand.h" +#include "cli_config.h" +#include "cliutils.h" +#include "qio.h" +#include "climsghandler.h" +#include "completionhelper.h" +#include "services/updatemanager.h" +#include "services/pluginmanager.h" +#include +#include +#include +#include + +bool listPlugins = false; + +QString cliHandleCmdLineArgs() +{ + QCommandLineParser parser; + parser.setApplicationDescription(QObject::tr("Command line interface to SQLiteStudio, a SQLite manager.")); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption debugOption({"d", "debug"}, QObject::tr("Enables debug messages on standard error output.")); + QCommandLineOption lemonDebugOption("debug-lemon", QObject::tr("Enables Lemon parser debug messages for SQL code assistant.")); + QCommandLineOption listPluginsOption("list-plugins", QObject::tr("Lists plugins installed in the SQLiteStudio end exists.")); + parser.addOption(debugOption); + parser.addOption(lemonDebugOption); + parser.addOption(listPluginsOption); + + parser.addPositionalArgument(QObject::tr("file"), QObject::tr("Database file to open")); + + parser.process(qApp->arguments()); + + if (parser.isSet(debugOption)) + setCliDebug(true); + + if (parser.isSet(listPluginsOption)) + listPlugins = true; + + CompletionHelper::enableLemonDebug = parser.isSet(lemonDebugOption); + + QStringList args = parser.positionalArguments(); + if (args.size() > 0) + return args[0]; + + return QString::null; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + int retCode = 1; + if (UpdateManager::handleUpdateOptions(a.arguments(), retCode)) + return retCode; + + QCoreApplication::setApplicationName("SQLiteStudio"); + QCoreApplication::setApplicationVersion(SQLITESTUDIO->getVersionString()); + + qInstallMessageHandler(cliMessageHandler); + + QString dbToOpen = cliHandleCmdLineArgs(); + + CliResultsDisplay::staticInit(); + initCliUtils(); + + SQLITESTUDIO->init(a.arguments(), false); + SQLITESTUDIO->initPlugins(); + + if (listPlugins) + { + for (const PluginManager::PluginDetails& details : PLUGINS->getAllPluginDetails()) + qOut << details.name << " " << details.versionString << "\n"; + + return 0; + } + + CliCommandExecutor executor; + + QObject::connect(CLI::getInstance(), &CLI::execCommand, &executor, &CliCommandExecutor::execCommand); + QObject::connect(&executor, &CliCommandExecutor::executionComplete, CLI::getInstance(), &CLI::executionComplete); + + if (!dbToOpen.isEmpty()) + CLI::getInstance()->openDbFile(dbToOpen); + + CLI::getInstance()->start(); + + return a.exec(); +} diff --git a/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro b/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro new file mode 100644 index 0000000..1b11aa2 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro @@ -0,0 +1,95 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-02-28T23:21:43 +# +#------------------------------------------------- + +include($$PWD/../dirs.pri) + +OBJECTS_DIR = $$OBJECTS_DIR/sqlitestudiocli +MOC_DIR = $$MOC_DIR/sqlitestudiocli +UI_DIR = $$UI_DIR/sqlitestudiocli + +QT += core +QT -= gui + +TARGET = sqlitestudiocli +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +CONFIG += c++11 +QMAKE_CXXFLAGS += -pedantic +linux|portable { + QMAKE_LFLAGS += -Wl,-rpath,./lib +} + +SOURCES += main.cpp \ + cli.cpp \ + commands/clicommand.cpp \ + commands/clicommandfactory.cpp \ + commands/clicommandadd.cpp \ + commands/clicommandremove.cpp \ + commands/clicommandexit.cpp \ + commands/clicommanddblist.cpp \ + commands/clicommanduse.cpp \ + commands/clicommandopen.cpp \ + commands/clicommandclose.cpp \ + commands/clicommandsql.cpp \ + clicommandexecutor.cpp \ + cli_config.cpp \ + commands/clicommandhelp.cpp \ + cliutils.cpp \ + commands/clicommandtables.cpp \ + climsghandler.cpp \ + commands/clicommandmode.cpp \ + commands/clicommandnullvalue.cpp \ + commands/clicommandhistory.cpp \ + commands/clicommanddir.cpp \ + commands/clicommandpwd.cpp \ + commands/clicommandcd.cpp \ + clicommandsyntax.cpp \ + commands/clicommandtree.cpp \ + clicompleter.cpp \ + commands/clicommanddesc.cpp + +LIBS += -lcoreSQLiteStudio + +win32: { + INCLUDEPATH += $$PWD/../../../include + LIBS += -L$$PWD/../../../lib -ledit_static +} + +unix: { + LIBS += -lreadline -ltermcap +} + +HEADERS += \ + cli.h \ + commands/clicommand.h \ + commands/clicommandfactory.h \ + commands/clicommandadd.h \ + commands/clicommandremove.h \ + commands/clicommandexit.h \ + commands/clicommanddblist.h \ + commands/clicommanduse.h \ + commands/clicommandopen.h \ + commands/clicommandclose.h \ + commands/clicommandsql.h \ + cli_config.h \ + clicommandexecutor.h \ + commands/clicommandhelp.h \ + cliutils.h \ + commands/clicommandtables.h \ + climsghandler.h \ + commands/clicommandmode.h \ + commands/clicommandnullvalue.h \ + commands/clicommandhistory.h \ + commands/clicommanddir.h \ + commands/clicommandpwd.h \ + commands/clicommandcd.h \ + clicommandsyntax.h \ + commands/clicommandtree.h \ + clicompleter.h \ + commands/clicommanddesc.h diff --git a/SQLiteStudio3/utils.pri b/SQLiteStudio3/utils.pri new file mode 100644 index 0000000..a689216 --- /dev/null +++ b/SQLiteStudio3/utils.pri @@ -0,0 +1,36 @@ +defineTest(copy_dir) { + #message("copying $$absolute_path($$1) to $$absolute_path($$2)"); + unix: { + system(cp -R $$quote($$1) $$quote($$2)) + } + win32: { + system(xcopy \"$$quote($$1)\" \"$$quote($$2)\" /s /e /y /q /i) + } +} + +defineTest(copy_file) { + #message("copying $$absolute_path($$1) to $$absolute_path($$2)"); + unix: { + system(cp $$quote($$1) $$quote($$2)) + } + win32: { + system(copy \"$$quote($$1)\" \"$$quote($$2)\" /y) + } +} + +# This would be better way, but targets defined inside of test function are not visible outside +# and they cannot be exported. I need to find another way to do this. +# +#defineTest(copy_file) { +# message("copying $$absolute_path($$1) to $$absolute_path($$2)"); +# unix: { +# copy_target.commands = cp $$quote($$absolute_path($$1)) $$quote($$absolute_path($$2)) +# } +# win32: { +# copy_target.commands = copy $$quote($$absolute_path($$1)) $$quote($$absolute_path($$2)) /y +# } +# QMAKE_EXTRA_TARGETS += copy_target +# PRE_TARGETDEPS += copy_target +# export(PRE_TARGETDEPS) +# export(QMAKE_EXTRA_TARGETS) +#} -- cgit v1.2.3