summaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@ubuntu.com>2014-12-06 17:33:25 -0500
committerLibravatarUnit 193 <unit193@ubuntu.com>2014-12-06 17:33:25 -0500
commit7167ce41b61d2ba2cdb526777a4233eb84a3b66a (patch)
treea35c14143716e1f2c98f808c81f89426045a946f /SQLiteStudio3/coreSQLiteStudio
Imported Upstream version 2.99.6upstream/2.99.6
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio')
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/TODO.txt66
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/committable.cpp41
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/committable.h27
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/bihash.h302
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h362
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/column.cpp38
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/column.h26
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/global.h66
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/memoryusage.cpp83
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/memoryusage.h6
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/nulldevice.cpp19
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h15
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/objectpool.h84
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp103
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.h65
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h164
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/strhash.h182
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/table.cpp52
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/table.h32
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/unused.h6
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/utils.cpp855
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/utils.h244
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp552
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h71
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp439
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/completioncomparer.h74
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp1425
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/completionhelper.h264
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder.h76
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.cpp99
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.h53
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.cpp189
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.h123
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.cpp28
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.h23
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.cpp120
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.h51
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro421
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio_global.h19
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/coresqlitestudio.qrc21
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/csvformat.cpp13
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/csvformat.h18
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp94
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/csvserializer.h15
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/datatype.cpp231
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/datatype.h72
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp879
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h485
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h882
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h1157
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.cpp62
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.h126
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp20
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/attachguard.h24
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.cpp200
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h359
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/db.cpp57
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/db.h831
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h97
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/dbsqlite.h.autosave72
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp11
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h33
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp336
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h81
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp784
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h1369
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp216
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h81
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.cpp16
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.h28
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp99
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h62
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp256
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h72
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.cpp22
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.h19
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp32
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.h24
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.cpp26
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.h14
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp149
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.h85
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.cpp28
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.h18
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp30
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.h22
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.cpp79
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.h30
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.cpp48
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.h29
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp113
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h110
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp63
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h158
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.cpp26
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.h14
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp42
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.h33
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.cpp13
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h39
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.cpp55
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.h48
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp142
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h323
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.cpp42
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h102
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h83
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp11
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/dbattacher.h99
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.cpp777
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h152
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h12
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp1253
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/dbversionconverter.h133
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp76
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h31
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/dialect.h10
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.cpp2112
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.h631
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp73
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/expectedtoken.h107
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/exportworker.cpp516
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/exportworker.h60
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.cpp178
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.h94
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/importworker.cpp171
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/importworker.h44
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/interruptable.h23
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/licenses/diff_match.txt18
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/licenses/fugue_icons.txt80
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt675
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt502
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/licenses/sqlitestudio_license.txt15
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/log.cpp47
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/log.h16
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.cpp128
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.h46
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.cpp80
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.h29
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.cpp63
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.h28
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.cpp71
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.h38
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.cpp88
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.h30
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.cpp48
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.h25
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.cpp37
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.h20
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.cpp92
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.h32
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.cpp190
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.h50
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp771
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h205
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.cpp381
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.h96
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.cpp120
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.h35
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.cpp120
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.h42
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.cpp54
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.h27
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.cpp137
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.h45
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.cpp48
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.h27
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.cpp78
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.h29
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.cpp86
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.h31
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.cpp79
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.h29
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.cpp85
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.h29
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.cpp27
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.h19
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp644
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.h144
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.cpp187
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.h75
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.cpp52
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.h30
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.cpp214
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.h57
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.cpp79
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.h31
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.cpp41
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.h28
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.cpp109
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.h41
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.cpp51
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.h30
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.cpp66
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.h41
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.cpp70
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.h38
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.cpp82
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.h31
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.cpp39
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.h26
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.cpp57
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.h28
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.cpp32
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.h25
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.cpp783
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.h228
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.cpp25
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.h17
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.cpp553
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h339
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitetablerelatedddl.h14
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.cpp207
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.h51
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.cpp56
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.h28
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.cpp87
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.h45
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp324
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/keywords.h123
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/lempar.c1021
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp314
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/lexer.h254
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.cpp470
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.h27
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp323
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/parser.h360
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.cpp45
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.h118
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.cpp184
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.h289
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/parsererror.cpp44
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/parsererror.h80
-rwxr-xr-xSQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh8
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.cpp4650
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.h146
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.y2068
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.cpp5262
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.h167
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.y2406
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.cpp206
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.h290
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/token.cpp621
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/parser/token.h733
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp35
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/pluginloader.h35
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp58
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h147
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h16
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h14
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h73
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp47
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h24
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h372
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h20
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp160
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h56
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp67
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h126
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h132
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h182
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp45
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h24
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp52
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h63
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp48
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h44
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui37
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp94
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h52
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui123
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h70
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp55
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h47
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui106
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp91
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h52
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui181
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp125
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h63
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui112
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp53
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h47
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui64
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h50
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp276
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h72
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.pngbin0 -> 1750 bytes
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp145
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h44
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp146
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h46
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.pngbin0 -> 376 bytes
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp29
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h16
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h56
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/pluginservicebase.cpp21
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h95
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/populateworker.cpp110
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/populateworker.h41
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/qio.cpp5
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/qio.h33
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/querymodel.cpp63
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/querymodel.h42
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/returncode.cpp51
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/returncode.h26
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp1151
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h294
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp37
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/Key.h59
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp37
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h58
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.cpp198
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.h71
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp394
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h130
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp910
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/schemaresolver.h220
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp673
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/selectresolver.h299
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp202
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h62
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/codeformatter.cpp91
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/codeformatter.h30
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/collationmanager.h41
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/config.cpp9
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/config.h178
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/dbmanager.cpp17
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h288
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/exportmanager.cpp283
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/exportmanager.h234
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.cpp28
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.h21
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/functionmanager.cpp39
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h74
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp125
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.h36
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp770
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h127
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp524
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h183
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp694
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.h96
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp820
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.h321
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/importmanager.cpp104
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/importmanager.h85
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/notifymanager.cpp85
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/notifymanager.h58
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h528
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp93
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h48
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp1058
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h137
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp42
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h17
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp390
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h258
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp695
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/tablemodifier.h114
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp103
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/tsvserializer.h20
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp127
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/viewmodifier.h53
364 files changed, 73428 insertions, 0 deletions
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 <QDebug>
+
+Committable::ConfirmFunction Committable::confirmFunc = nullptr;
+QList<Committable*> 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<Committable*> 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 <QList>
+#include <functional>
+
+class API_EXPORT Committable
+{
+ public:
+ typedef std::function<bool(const QList<Committable*>& 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<Committable*> 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 <QHash>
+
+/**
+ * @brief Bi-directional QHash
+ *
+ * Bi-directional hash treats both inserted values as keys to each other.
+ * Bi-directional hash built on the <tt>left</tt> and <tt>right</tt> 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 L, class R>
+class BiHash
+{
+ public:
+ /**
+ * @brief Creates empty hash.
+ */
+ BiHash() {}
+
+ /**
+ * @brief Creates hash initialized with given values.
+ * @param list C++11 style initializer list, like: <tt>{{x=y}, {a=b}}</tt>
+ */
+ BiHash(std::initializer_list<std::pair<L, R>> list)
+ {
+ hash = QHash<L,R>(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<L, R> & other)
+ {
+ unite(other);
+ }
+
+ /**
+ * @brief Creates copy of BiHash.
+ * @param other BiHash to copy.
+ */
+ BiHash(const BiHash<L, R> & 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<L,R>& unite(const BiHash<L,R>& other)
+ {
+ unite(other.hash);
+ return *this;
+ }
+
+ /**
+ * @overload
+ */
+ BiHash<L,R>& unite(const QHash<L,R>& other)
+ {
+ QHashIterator<L, R> 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<L> leftValues() const
+ {
+ return hash.keys();
+ }
+
+ /**
+ * @brief Provides all right values.
+ * @return List of values from right side.
+ */
+ QList<R> rightValues() const
+ {
+ return inverted.keys();
+ }
+
+ /**
+ * @brief Provides java-like iterator for the hash.
+ * @return Iterator object for this hash.
+ */
+ QHashIterator<L,R> iterator() const
+ {
+ return QHashIterator<L,R>(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<L,R>& toQHash() const
+ {
+ return hash;
+ }
+
+ /**
+ * @brief Provides QHash with inverted values (right-to-left)
+ * @return QHash with right values as keys.
+ */
+ const QHash<R,L>& toInvertedQHash() const
+ {
+ return inverted;
+ }
+
+ private:
+ /**
+ * @brief Fills inverted internal hash basing on values from hash class member.
+ */
+ void initInverted()
+ {
+ QHashIterator<L,R> it(hash);
+ while (it.hasNext())
+ {
+ it.next();
+ inverted[it.value()] = it.key();
+ }
+ }
+
+ /**
+ * @brief Hash containing left-to-right mapping.
+ */
+ QHash<L,R> hash;
+
+ /**
+ * @brief Hash containing right-to-left mapping.
+ */
+ QHash<R,L> 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 <QHash>
+#include <QString>
+
+/**
+ * @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: <tt>{{"x"="y"}, {"a"="b"}}</tt>
+ */
+ BiStrHash(std::initializer_list<std::pair<QString, QString>> list)
+ {
+ hash = QHash<QString,QString>(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<QString, QString> & 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<QString,QString>& other)
+ {
+ QHashIterator<QString, QString> 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<QString,QString> iterator() const
+ {
+ return QHashIterator<QString,QString>(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<QString,QString> 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<QString,QString> hash;
+
+ /**
+ * @brief Right to left mapping.
+ */
+ QHash<QString,QString> inverted;
+
+ /**
+ * @brief Lower left to true left key mapping.
+ */
+ QHash<QString,QString> lowerHash;
+
+ /**
+ * @brief Lower right to true right key mapping.
+ */
+ QHash<QString,QString> 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 <QHash>
+
+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 <QString>
+
+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 <QtGlobal>
+
+#ifdef Q_OS_LINUX
+#include <QFile>
+#include <QRegularExpression>
+#else
+
+#ifdef Q_OS_WIN32
+#include "windows.h"
+#include "psapi.h"
+#else
+
+#ifdef Q_OS_MAC
+#include <mach/mach.h>
+#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 <QIODevice>
+
+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 <QHash>
+#include <QHashIterator>
+#include <QMutex>
+#include <QWaitCondition>
+
+template <class T>
+class ObjectPool
+{
+ public:
+ ObjectPool(quint32 min, quint32 max);
+
+ T* reserve();
+ void release(T* obj);
+
+ private:
+ QHash<T*, bool> pool;
+ QMutex mutex;
+ QWaitCondition waitCond;
+ int min;
+ int max;
+};
+
+template <class T>
+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<T*, bool> 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 <class T>
+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 <QReadWriteLock>
+#include <QReadLocker>
+#include <QWriteLocker>
+
+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 <QHash>
+
+/**
+ * @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 Key, class T>
+class SortedHash : public QHash<Key, T>
+{
+ public:
+ SortedHash(std::initializer_list<std::pair<Key, T>> list) : QHash<Key, T>(list)
+ {
+ sortedKeys = keys();
+ }
+
+ SortedHash(const QHash<Key, T>& other) : QHash<Key, T>(other)
+ {
+ sortedKeys = keys();
+ }
+
+ SortedHash(QHash<Key, T>&& other) : QHash<Key, T>(other)
+ {
+ sortedKeys = keys();
+ }
+
+ SortedHash() : QHash<Key, T>()
+ {
+ }
+
+ typename QHash<Key, T>::iterator insert(const Key& key, const T& value)
+ {
+ if (!sortedKeys.contains(key))
+ sortedKeys << key;
+
+ return QHash<Key, T>::insert(key, value);
+ }
+
+ int remove(const Key& key)
+ {
+ sortedKeys.removeOne(key);
+ return QHash<Key, T>::remove(key);
+ }
+
+ void swap(QHash<Key, T>& other)
+ {
+ QHash<Key, T>::swap(other);
+ sortedKeys = keys();
+ }
+
+ void swap(SortedHash<Key, T>& other)
+ {
+ QHash<Key, T>::swap(other);
+ sortedKeys = other.sortedKeys;
+ }
+
+ T take(const Key& key)
+ {
+ sortedKeys.removeOne(key);
+ return QHash<Key, T>::take(key);
+ }
+
+ QList<Key> keys() const
+ {
+ return sortedKeys;
+ }
+
+ QList<Key> keys(const T& value) const
+ {
+ QList<Key> results;
+ foreach (const Key& k, sortedKeys)
+ if (value(k) == value)
+ results << k;
+
+ return results;
+ }
+
+ SortedHash<Key, T>& unite(const QHash<Key, T>& other)
+ {
+ QHash<Key, T>::unite(other);
+ sortedKeys += other.keys();
+ return *this;
+ }
+
+ SortedHash<Key, T>& unite(const SortedHash<Key, T>& other)
+ {
+ QHash<Key, T>::unite(other);
+ sortedKeys += other.sortedKeys;
+ return *this;
+ }
+
+ QList<T> values() const
+ {
+ QList<T> results;
+ foreach (const Key& k, sortedKeys)
+ results << value(k);
+
+ return results;
+ }
+
+ bool operator!=(const SortedHash<Key, T>& other) const
+ {
+ return !operator==(other);
+ }
+
+ SortedHash<Key, T>& operator=(const QHash<Key, T>& other)
+ {
+ QHash<Key, T>::operator=(other);
+ sortedKeys = other.keys();
+ return *this;
+ }
+
+ SortedHash<Key, T>& operator=(const SortedHash<Key, T>& other)
+ {
+ QHash<Key, T>::operator=(other);
+ sortedKeys = other.sortedKeys;
+ return *this;
+ }
+
+ bool operator==(const SortedHash<Key, T>& other) const
+ {
+ return QHash<Key, T>::operator==(other) && sortedKeys == other.sortedKeys;
+ }
+
+ T & operator[](const Key& key)
+ {
+ if (!sortedKeys.contains(key))
+ sortedKeys << key;
+
+ return QHash<Key, T>::operator[](key);
+ }
+
+ const T operator[](const Key& key) const
+ {
+ return QHash<Key, T>::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<Key> 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 <QHash>
+#include <QString>
+#include <QStringList>
+#include <QDebug>
+
+template <class T>
+class StrHash
+{
+ public:
+ StrHash() {}
+ StrHash(std::initializer_list<std::pair<QString,T>> list) : hash(QHash<QString,T>(list))
+ {
+ initLower();
+ }
+
+ StrHash(const QHash<QString,T>& other) : hash(QHash<QString,T>(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<T>& unite(const StrHash<T>& other)
+ {
+ unite(other.hash);
+ return *this;
+ }
+
+ StrHash<T>& unite(const QHash<QString,T>& other)
+ {
+ QHashIterator<QString,T> 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<T> values() const
+ {
+ return hash.values();
+ }
+
+ QStringList keys() const
+ {
+ return hash.keys();
+ }
+
+ QHashIterator<QString,T> iterator() const
+ {
+ return QHashIterator<QString,T>(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<QString,T> it(hash);
+ while (it.hasNext())
+ {
+ it.next();
+ lowerCaseHash[it.key().toLower()] = it.key();
+ }
+ }
+
+ QHash<QString,QString> lowerCaseHash;
+ QHash<QString,T> 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 <QHash>
+
+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 <QString>
+
+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 <QTextCodec>
+#include <QString>
+#include <QSet>
+#include <QVariant>
+#include <QDateTime>
+#include <QSysInfo>
+#include <QDebug>
+#include <QRegularExpression>
+#include <QDir>
+
+#ifdef Q_OS_LINUX
+#include <sys/utsname.h>
+
+#include <QFileInfo>
+#endif
+
+void initUtils()
+{
+ qRegisterMetaType<QList<int>>("QList<int>");
+ qRegisterMetaType<DbObjectType>("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<QString> 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<QString,QVariant>& hash)
+{
+ QByteArray bytes;
+ QDataStream stream(&bytes, QIODevice::WriteOnly);
+ stream << QVariant(hash);
+ return bytes;
+}
+
+QHash<QString,QVariant> bytesToHash(const QByteArray& bytes)
+{
+ if (bytes.isNull())
+ return QHash<QString,QVariant>();
+
+ 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<QByteArray> codecs = QTextCodec::availableCodecs();
+ QStringList names;
+ QSet<QString> 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<int>& 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 <QList>
+#include <QMutableListIterator>
+#include <QSet>
+#include <QChar>
+#include <QStringList>
+#include <QFileInfo>
+
+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<QString> 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<QString,QVariant>& hash);
+API_EXPORT QHash<QString,QVariant> 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 <class T>
+QList<T> filter(const QList<T>& list, std::function<bool(const T& value)> filterFunction)
+{
+ QList<T> results;
+ for (const T& value : list)
+ {
+ if (filterFunction(value))
+ results << value;
+ }
+ return results;
+}
+
+template <class T>
+bool contains(const QList<T>& list, std::function<bool(const T& value)> 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<int>& 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 <class T>
+QList<T> reverse(const QList<T>& list)
+{
+ QList<T> result;
+ for (const T& el : list)
+ result.prepend(el);
+
+ return result;
+}
+
+template <class T>
+void removeDuplicates(QList<T>& list)
+{
+ QSet<T> set;
+ QMutableListIterator<T> i(list);
+ while (i.hasNext())
+ {
+ i.next();
+ if (set.contains(i.value()))
+ i.remove();
+ else
+ set << i.value();
+ }
+}
+
+Q_DECLARE_METATYPE(QList<int>)
+
+#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 <QHash>
+#include <QPair>
+#include <QString>
+#include <QDebug>
+#include <QMetaType>
+
+QString invalidIdCharacters = "[]()$\"'@*.,+-=/%&|:; \t\n<>";
+QHash<NameWrapper,QPair<QChar,QChar> > wrapperChars;
+QList<NameWrapper> sqlite3Wrappers;
+QList<NameWrapper> sqlite2Wrappers;
+
+void initUtilsSql()
+{
+ wrapperChars[NameWrapper::BRACKET] = QPair<QChar,QChar>('[', ']');
+ wrapperChars[NameWrapper::QUOTE] = QPair<QChar,QChar>('\'', '\'');
+ wrapperChars[NameWrapper::BACK_QUOTE] = QPair<QChar,QChar>('`', '`');
+ wrapperChars[NameWrapper::DOUBLE_QUOTE] = QPair<QChar,QChar>('"', '"');
+
+ sqlite3Wrappers << NameWrapper::DOUBLE_QUOTE
+ << NameWrapper::BRACKET
+ << NameWrapper::QUOTE
+ << NameWrapper::BACK_QUOTE;
+ sqlite2Wrappers << NameWrapper::DOUBLE_QUOTE
+ << NameWrapper::BRACKET
+ << NameWrapper::QUOTE;
+
+ qRegisterMetaType<SqlQueryPtr>("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<QChar,QChar> 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<QChar,QChar> getQuoteCharacter(QString& obj, Dialect dialect, NameWrapper favWrapper)
+{
+ QList<NameWrapper> 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<QChar,QChar> 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<QChar,QChar>();
+}
+
+QList<QString> wrapObjNames(const QList<QString>& objList, Dialect dialect, NameWrapper favWrapper)
+{
+ QList<QString> results;
+ for (int i = 0; i < objList.size(); i++)
+ results << wrapObjName(objList[i], dialect, favWrapper);
+
+ return results;
+}
+
+QList<QString> wrapObjNamesIfNeeded(const QList<QString>& objList, Dialect dialect, NameWrapper favWrapper)
+{
+ QList<QString> results;
+ for (int i = 0; i < objList.size(); i++)
+ results << wrapObjIfNeeded(objList[i], dialect, favWrapper);
+
+ return results;
+}
+
+QList<NameWrapper> 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<NameWrapper> wrappers;
+
+ if (dialect == Dialect::Sqlite2)
+ wrappers = sqlite2Wrappers;
+ else
+ wrappers = sqlite3Wrappers;
+
+ foreach (NameWrapper wrapper, wrappers)
+ {
+ QPair<QChar,QChar> 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<NameWrapper> wrappers;
+ if (dialect == Dialect::Sqlite2)
+ wrappers = sqlite2Wrappers;
+ else
+ wrappers = sqlite3Wrappers;
+
+ foreach (NameWrapper wrapper, wrappers)
+ {
+ QPair<QChar,QChar> 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<TokenList> splitQueries(const TokenList& tokenizedQuery, bool* complete)
+{
+ QList<TokenList> 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<TokenList> 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<QueryWithParamNames> getQueriesWithParamNames(const QString& query, Dialect dialect)
+{
+ QList<QueryWithParamNames> results;
+
+ TokenList allTokens = Lexer::tokenize(query, dialect);
+ QList<TokenList> 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<QueryWithParamCount> getQueriesWithParamCount(const QString& query, Dialect dialect)
+{
+ QList<QueryWithParamCount> results;
+
+ TokenList allTokens = Lexer::tokenize(query, dialect);
+ QList<TokenList> 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 <QString>
+#include <QChar>
+#include <QPair>
+
+// TODO: unit tests for most of methods from this module
+
+enum class NameWrapper
+{
+ DOUBLE_QUOTE,
+ QUOTE,
+ BACK_QUOTE,
+ BRACKET,
+ null
+};
+
+typedef QPair<QString,QStringList> QueryWithParamNames;
+typedef QPair<QString,int> 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<QChar,QChar> getQuoteCharacter(QString& obj, Dialect dialect,
+ NameWrapper favWrapper = NameWrapper::null);
+API_EXPORT QList<QString> wrapObjNames(const QList<QString>& objList, Dialect dialect = Dialect::Sqlite3, NameWrapper favWrapper = NameWrapper::null);
+API_EXPORT QList<QString> wrapObjNamesIfNeeded(const QList<QString>& objList, Dialect dialect, NameWrapper favWrapper = NameWrapper::null);
+API_EXPORT QList<NameWrapper> 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<TokenList> 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<QueryWithParamNames> getQueriesWithParamNames(const QString& query, Dialect dialect);
+API_EXPORT QList<QueryWithParamCount> 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 <QDebug>
+
+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<QStringList> &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<QStringList>& 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<SelectResolver::Column> &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<SelectResolver::Column> 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<QStringList>& 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<QStringList>& 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<SelectResolver::Column>& 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 <QStringList>
+#include <QDebug>
+
+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<ExpectedTokenPtr> 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<ExpectedTokenPtr> CompletionHelper::getExpectedTokens(TokenPtr token)
+{
+ QList<ExpectedTokenPtr> 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<ExpectedTokenPtr> CompletionHelper::getTables()
+{
+ QString dbName;
+ if (!validatePreviousIdForGetObjects(&dbName))
+ return QList<ExpectedTokenPtr>();
+
+ QList<ExpectedTokenPtr> 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<ExpectedTokenPtr> CompletionHelper::getIndexes()
+{
+ if (!validatePreviousIdForGetObjects())
+ return QList<ExpectedTokenPtr>();
+
+ return getObjects(ExpectedToken::INDEX);
+}
+
+QList<ExpectedTokenPtr> CompletionHelper::getTriggers()
+{
+ if (!validatePreviousIdForGetObjects())
+ return QList<ExpectedTokenPtr>();
+
+ return getObjects(ExpectedToken::TRIGGER);
+}
+
+QList<ExpectedTokenPtr> CompletionHelper::getViews()
+{
+ if (!validatePreviousIdForGetObjects())
+ return QList<ExpectedTokenPtr>();
+
+ return getObjects(ExpectedToken::VIEW);
+}
+
+QList<ExpectedTokenPtr> CompletionHelper::getDatabases()
+{
+ QList<ExpectedTokenPtr> results;
+
+ results += getExpectedToken(ExpectedToken::DATABASE, "main", "main", tr("Default database"));
+ results += getExpectedToken(ExpectedToken::DATABASE, "temp", "temp", tr("Temporary objects database"));
+
+ QSet<QString> 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<ExpectedTokenPtr> CompletionHelper::getObjects(ExpectedToken::Type type)
+{
+ if (previousId)
+ return getObjects(type, previousId->value);
+ else
+ return getObjects(type, QString());
+}
+
+QList<ExpectedTokenPtr> 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<ExpectedTokenPtr>();
+ }
+
+ QList<ExpectedTokenPtr> results;
+ foreach (QString object, schemaResolver->getObjects(dbName, typeStr))
+ results << getExpectedToken(type, object, originalDbName);
+
+ return results;
+}
+
+QList<ExpectedTokenPtr> CompletionHelper::getColumns()
+{
+ QList<ExpectedTokenPtr> 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<ExpectedTokenPtr> CompletionHelper::getColumnsNoPrefix()
+{
+ QList<ExpectedTokenPtr> 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<QString,QStringList> 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<QString,QStringList> it(columnList);
+ while (it.hasNext())
+ {
+ it.next();
+ results << getColumnsNoPrefix(it.key(), it.value());
+ }
+
+ return results;
+}
+
+QList<ExpectedTokenPtr> CompletionHelper::getColumnsNoPrefix(const QString& column, const QStringList& tables)
+{
+ QList<ExpectedTokenPtr> 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<ExpectedTokenPtr> CompletionHelper::getColumns(const QString &prefixTable)
+{
+ QList<ExpectedTokenPtr> 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<SqliteCreateTrigger>();
+ 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<ExpectedTokenPtr> CompletionHelper::getColumns(const QString &prefixDb, const QString &prefixTable)
+{
+ QList<ExpectedTokenPtr> 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<ExpectedTokenPtr> CompletionHelper::getFavoredColumns(const QList<ExpectedTokenPtr>& 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<SqliteCreateTable>()->table;
+
+ QList<ExpectedTokenPtr> results;
+ foreach (const QString& column, columnsToAdd)
+ results << getExpectedToken(ExpectedToken::COLUMN, column, ctxInfo);
+
+ return results;
+}
+
+QList<ExpectedTokenPtr> 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<ExpectedTokenPtr> expectedTokens;
+ foreach (QString function, functions)
+ expectedTokens += getExpectedToken(ExpectedToken::FUNCTION, function);
+
+ return expectedTokens;
+}
+
+QList<ExpectedTokenPtr> CompletionHelper::getPragmas(Dialect dialect)
+{
+ QStringList pragmas;
+ if (dialect == Dialect::Sqlite2)
+ pragmas = sqlite2Pragmas;
+ else
+ pragmas = sqlite3Pragmas;
+
+ QList<ExpectedTokenPtr> expectedTokens;
+ foreach (QString pragma, pragmas)
+ expectedTokens += getExpectedToken(ExpectedToken::PRAGMA, pragma);
+
+ return expectedTokens;
+}
+
+QList<ExpectedTokenPtr> CompletionHelper::getCollations()
+{
+ SqlQueryPtr results = db->exec("PRAGMA collation_list;");
+ if (results->isError())
+ {
+ qWarning() << "Got error when trying to get collation_list: "
+ << results->getErrorText();
+ }
+ QList<ExpectedTokenPtr> 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<TokenPtr> 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<ExpectedTokenPtr> &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<ExpectedTokenPtr> 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<ExpectedTokenPtr> &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<ExpectedTokenPtr> it(resultsSoFar);
+ while (it.hasNext())
+ {
+ ExpectedTokenPtr token = it.next();
+ if (token->type == ExpectedToken::OTHER)
+ it.remove();
+ }
+}
+
+void CompletionHelper::filterDuplicates(QList<ExpectedTokenPtr>& resultsSoFar)
+{
+ QSet<ExpectedTokenPtr> set = resultsSoFar.toSet();
+ resultsSoFar = set.toList();
+}
+
+void CompletionHelper::applyFilter(QList<ExpectedTokenPtr>& resultsSoFar, const QString& filter)
+{
+ if (filter.isEmpty())
+ return;
+
+ QMutableListIterator<ExpectedTokenPtr> 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<SqliteQuery*>(parsedQuery->clone()));
+ }
+}
+
+void CompletionHelper::sort(QList<ExpectedTokenPtr> &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<Context> 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<SqliteExpr*>(stmt))
+ stmt = stmt->parentStatement();
+
+ return (stmt && dynamic_cast<SqliteExpr*>(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<SqliteSelect::Core*>(stmt))
+ stmt = stmt->parentStatement();
+
+ if (stmt && dynamic_cast<SqliteSelect::Core*>(stmt))
+ {
+ // We found our select core
+ return dynamic_cast<SqliteSelect::Core*>(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<SqliteSelect::Core*>(stmt))
+ stmt = stmt->parentStatement();
+
+ if (!stmt || !dynamic_cast<SqliteSelect::Core*>(stmt))
+ return;
+
+ // We got another select core at higher level
+ parentCore = dynamic_cast<SqliteSelect::Core*>(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<SqliteCreateTable>();
+ foreach (SqliteCreateTable::Column* col, createTable->columns)
+ favoredColumnNames << col->name;
+}
+
+QList<ExpectedTokenPtr> CompletionHelper::Results::filtered()
+{
+ QList<ExpectedTokenPtr> 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 <QObject>
+#include <QSet>
+
+class DbAttacher;
+
+class API_EXPORT CompletionHelper : public QObject
+{
+ friend class CompletionComparer;
+
+ public:
+ struct API_EXPORT Results
+ {
+ QList<ExpectedTokenPtr> filtered();
+
+ QList<ExpectedTokenPtr> 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<ExpectedTokenPtr> &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<ExpectedTokenPtr> 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<ExpectedTokenPtr> getTables();
+ QList<ExpectedTokenPtr> getIndexes();
+ QList<ExpectedTokenPtr> getTriggers();
+ QList<ExpectedTokenPtr> getViews();
+ QList<ExpectedTokenPtr> getDatabases();
+ QList<ExpectedTokenPtr> getObjects(ExpectedToken::Type type);
+ QList<ExpectedTokenPtr> getObjects(ExpectedToken::Type type, const QString& database);
+ QList<ExpectedTokenPtr> getColumns();
+ QList<ExpectedTokenPtr> getColumnsNoPrefix();
+ QList<ExpectedTokenPtr> getColumnsNoPrefix(const QString &column, const QStringList &tables);
+ QList<ExpectedTokenPtr> getColumns(const QString& prefixTable);
+ QList<ExpectedTokenPtr> getColumns(const QString& prefixDb, const QString& prefixTable);
+ QList<ExpectedTokenPtr> getFavoredColumns(const QList<ExpectedTokenPtr>& resultsSoFar);
+
+ QList<ExpectedTokenPtr> getPragmas(Dialect dialect);
+ QList<ExpectedTokenPtr> getFunctions(Db* db);
+ QList<ExpectedTokenPtr> 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<ExpectedTokenPtr> &results, const TokenList& tokens);
+ void filterOtherId(QList<ExpectedTokenPtr> &results, const TokenList& tokens);
+ void filterDuplicates(QList<ExpectedTokenPtr> &results);
+ bool isFilterType(Token::Type type);
+ void parseFullSql();
+ void sort(QList<ExpectedTokenPtr> &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 <class T>
+ bool cursorFitsInCollection(const QList<T*>& 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 <class T>
+ 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<QString,QStringList> tableToAlias;
+
+ /**
+ * @brief aliasToTable
+ * Maps table alias to table's real name.
+ */
+ //QHash<QString,QString> aliasToTable;
+ StrHash<Table> 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<SelectResolver::Column> selectAvailableColumns;
+
+ /**
+ * @brief selectAvailableTables
+ * Availble tables are tables mentioned in FROM clause.
+ */
+ QSet<SelectResolver::Table> selectAvailableTables;
+
+ /**
+ * @brief parentSelectCores
+ * List of all parent select core objects in order: from direct parent, to the oldest parent.
+ */
+ QList<SqliteSelect::Core*> parentSelectCores;
+
+ /**
+ * @brief parentSelectAvailableColumns
+ * List of all columns available in all tables mentioned in all parent select cores.
+ */
+ QList<SelectResolver::Column> parentSelectAvailableColumns;
+
+ /**
+ * @brief parentSelectAvailableTables
+ * Same as parentSelectAvailableColumns, but for tables in FROM clauses.
+ */
+ QSet<SelectResolver::Table> 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<Type> Name = CfgTypedEntry<Type>(#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<QString, CfgEntry *> &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<CfgEntry*>(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 <QVariant>
+#include <QHash>
+#include <QString>
+#include <QObject>
+
+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<QString,CfgEntry*>& 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<QString,CfgEntry*> 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 <QDebug>
+
+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 <QString>
+#include <QVariant>
+#include <QObject>
+
+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 T>
+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<T>();
+ }
+
+ void set(const T& value)
+ {
+ CfgEntry::set(QVariant::fromValue<T>(value));
+ }
+};
+
+typedef CfgTypedEntry<QString> 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*>* CfgLazyInitializer::instances = nullptr;
+
+CfgLazyInitializer::CfgLazyInitializer(std::function<void ()> initFunc, const char *name) :
+ initFunc(initFunc)
+{
+ UNUSED(name);
+ if (!instances)
+ instances = new QList<CfgLazyInitializer*>();
+
+ *instances << this;
+}
+
+void CfgLazyInitializer::init()
+{
+ if (!instances)
+ instances = new QList<CfgLazyInitializer*>();
+
+ 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 <QList>
+#include <functional>
+
+class API_EXPORT CfgLazyInitializer
+{
+ public:
+ CfgLazyInitializer(std::function<void(void)> initFunc, const char* name);
+
+ static void init();
+
+ private:
+ void doInitialize();
+
+ std::function<void(void)> initFunc;
+
+ static QList<CfgLazyInitializer*>* 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*>* 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<CfgMain*>();
+
+ *instances << this;
+}
+
+CfgMain::~CfgMain()
+{
+ if (!instances)
+ instances = new QList<CfgMain*>();
+
+ instances->removeOne(this);
+}
+
+void CfgMain::staticInit()
+{
+ qRegisterMetaType<CfgMain*>("CfgMain*");
+ qRegisterMetaType<CfgCategory*>("CfgCategory*");
+ qRegisterMetaType<CfgEntry*>("CfgEntry*");
+}
+
+QList<CfgMain*> CfgMain::getInstances()
+{
+ if (!instances)
+ instances = new QList<CfgMain*>();
+
+ return *instances;
+}
+
+QList<CfgMain*> CfgMain::getPersistableInstances()
+{
+ QList<CfgMain*> list;
+ for (CfgMain* main : getInstances())
+ {
+ if (main->isPersistable())
+ list << main;
+ }
+ return list;
+}
+
+QHash<QString, CfgCategory *> &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 <QVariant>
+#include <QList>
+#include <QHash>
+#include <QString>
+
+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<CfgMain*> getInstances();
+ static QList<CfgMain*> getPersistableInstances();
+
+ QHash<QString,CfgCategory*>& 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<QString,CfgCategory*> childs;
+
+ static QList<CfgMain*>* 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 <QtCore/qglobal.h>
+
+#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 @@
+<RCC>
+ <qresource prefix="/forms">
+ <file>plugins/populatesequence.ui</file>
+ <file>plugins/populaterandomtext.ui</file>
+ <file>plugins/populatedictionary.ui</file>
+ <file>plugins/populateconstant.ui</file>
+ <file>plugins/populaterandom.ui</file>
+ <file>plugins/populatescript.ui</file>
+ </qresource>
+ <qresource prefix="/images">
+ <file>plugins/scriptingsql.png</file>
+ <file>plugins/scriptingqt.png</file>
+ </qresource>
+ <qresource prefix="/docs">
+ <file>licenses/fugue_icons.txt</file>
+ <file>licenses/sqlitestudio_license.txt</file>
+ <file>licenses/lgpl.txt</file>
+ <file>licenses/diff_match.txt</file>
+ <file>licenses/gpl.txt</file>
+ </qresource>
+</RCC>
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 <QtGlobal>
+
+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 <QString>
+
+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 <QStringList>
+
+// TODO write unit tests for CsvSerializer
+
+QString CsvSerializer::serialize(const QList<QStringList>& 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<QStringList> CsvSerializer::deserialize(const QString& data, const CsvFormat& format)
+{
+ QList<QStringList> 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<QStringList>& data, const CsvFormat& format);
+ static QString serialize(const QStringList& data, const CsvFormat& format);
+ static QList<QStringList> 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 <QMetaEnum>
+#include <QRegularExpression>
+
+QList<DataType::Enum> DataType::values = [=]() -> QList<DataType::Enum>
+{
+ QList<DataType::Enum> list;
+ QMetaEnum metaEnum = DataType::staticMetaObject.enumerator(0);
+ DataType::Enum value;
+ for (int i = 0; i < metaEnum.keyCount(); i++)
+ {
+ value = static_cast<DataType::Enum>(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<DataType::Enum>(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"("
+ "^(?<type>[^\)]*)\s*(\((?<scale>[\d\.]+)\s*(,\s*(?<precision>[\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<Enum>(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::Enum> 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 <QObject>
+#include <QVariant>
+
+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<Enum> getAllTypes();
+ static QStringList getAllNames();
+
+ private:
+ Enum type = unknown;
+ QVariant precision;
+ QVariant scale;
+ QString typeStr;
+
+ static QList<Enum> 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 <QDebug>
+#include <QTime>
+#include <QWriteLocker>
+#include <QReadLocker>
+#include <QThreadPool>
+#include <QMetaEnum>
+#include <QtConcurrent/QtConcurrentRun>
+
+quint32 AbstractDb::asyncId = 1;
+
+AbstractDb::AbstractDb(const QString& name, const QString& path, const QHash<QString, QVariant>& 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<QString, QVariant>& 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<QString, QVariant>& 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<QVariant>(), flags);
+}
+
+SqlQueryPtr AbstractDb::exec(const QString& query, const QVariant& arg)
+{
+ return exec(query, {arg});
+}
+
+SqlQueryPtr AbstractDb::exec(const QString& query, std::initializer_list<QVariant> argList)
+{
+ return exec(query, QList<QVariant>(argList));
+}
+
+SqlQueryPtr AbstractDb::exec(const QString &query, std::initializer_list<std::pair<QString, QVariant> > argMap)
+{
+ return exec(query, QHash<QString,QVariant>(argMap));
+}
+
+void AbstractDb::asyncExec(const QString &query, const QList<QVariant> &args, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags)
+{
+ quint32 asyncId = asyncExec(query, args, flags);
+ resultHandlers[asyncId] = resultsHandler;
+}
+
+void AbstractDb::asyncExec(const QString &query, const QHash<QString, QVariant> &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<QVariant>& args, Flags flags)
+{
+ return execListArg(query, args, flags);
+}
+
+SqlQueryPtr AbstractDb::exec(const QString& query, const QHash<QString, QVariant>& args, AbstractDb::Flags flags)
+{
+ return execHashArg(query, args, flags);
+}
+
+SqlQueryPtr AbstractDb::execHashArg(const QString& query, const QHash<QString,QVariant>& 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<QVariant>& 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<QString, QVariant> AbstractDb::getAggregateContext(void* memPtr)
+{
+ if (!memPtr)
+ {
+ qCritical() << "Could not allocate aggregate context.";
+ return QHash<QString, QVariant>();
+ }
+
+ QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr);
+ if (!*aggCtxPtr)
+ *aggCtxPtr = new QHash<QString,QVariant>();
+
+ return **aggCtxPtr;
+}
+
+void AbstractDb::setAggregateContext(void* memPtr, const QHash<QString, QVariant>& aggregateContext)
+{
+ if (!memPtr)
+ {
+ qCritical() << "Could not extract aggregate context.";
+ return;
+ }
+
+ QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr);
+ **aggCtxPtr = aggregateContext;
+}
+
+void AbstractDb::releaseAggregateContext(void* memPtr)
+{
+ if (!memPtr)
+ {
+ qCritical() << "Could not release aggregate context.";
+ return;
+ }
+
+ QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr);
+ delete *aggCtxPtr;
+}
+
+QVariant AbstractDb::evaluateScalar(void* dataPtr, const QList<QVariant>& argList, bool& ok)
+{
+ if (!dataPtr)
+ return QVariant();
+
+ FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr);
+
+ return FUNCTIONS->evaluateScalar(userData->name, userData->argCount, argList, userData->db, ok);
+}
+
+void AbstractDb::evaluateAggregateStep(void* dataPtr, QHash<QString, QVariant>& aggregateContext, QList<QVariant> argList)
+{
+ if (!dataPtr)
+ return;
+
+ FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr);
+
+ QHash<QString,QVariant> 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<QString, QVariant>& aggregateContext, bool& ok)
+{
+ if (!dataPtr)
+ return QVariant();
+
+ FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr);
+ QHash<QString,QVariant> 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<QVariant>(), flags);
+ return asyncExec(runner);
+}
+
+quint32 AbstractDb::asyncExec(const QString& query, const QHash<QString, QVariant>& args, AbstractDb::Flags flags)
+{
+ AsyncQueryRunner* runner = new AsyncQueryRunner(query, args, flags);
+ return asyncExec(runner);
+}
+
+quint32 AbstractDb::asyncExec(const QString& query, const QList<QVariant>& 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<Db *, QString> &AbstractDb::getAttachedDatabases()
+{
+ QReadLocker locker(&dbOperLock);
+ return attachedDbMap.toInvertedQHash();
+}
+
+QSet<QString> AbstractDb::getAllAttaches()
+{
+ QReadLocker locker(&dbOperLock);
+ QSet<QString> 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<QString> 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 <QObject>
+#include <QVariant>
+#include <QList>
+#include <QHash>
+#include <QSet>
+#include <QReadWriteLock>
+#include <QRunnable>
+#include <QStringList>
+
+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<QString, QVariant>& connOptions);
+
+ virtual ~AbstractDb();
+
+ bool isOpen();
+ QString getName();
+ QString getPath();
+ quint8 getVersion();
+ Dialect getDialect();
+ QString getEncoding();
+ QHash<QString,QVariant>& getConnectionOptions();
+ void setName(const QString& value);
+ void setPath(const QString& value);
+ void setConnectionOptions(const QHash<QString,QVariant>& value);
+ SqlQueryPtr exec(const QString& query, const QList<QVariant> &args, Flags flags = Flag::NONE);
+ SqlQueryPtr exec(const QString& query, const QHash<QString, QVariant>& 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<QVariant> argList);
+ SqlQueryPtr exec(const QString &query, std::initializer_list<std::pair<QString,QVariant>> argMap);
+ void asyncExec(const QString& query, const QList<QVariant>& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE);
+ void asyncExec(const QString& query, const QHash<QString, QVariant>& 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<QVariant>& args, Flags flags = Flag::NONE);
+ quint32 asyncExec(const QString& query, const QHash<QString, QVariant>& 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<Db*,QString>& getAttachedDatabases();
+ QSet<QString> 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<QString,QVariant> getAggregateContext(void* memPtr);
+ static void setAggregateContext(void* memPtr, const QHash<QString,QVariant>& 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<QVariant>& argList, bool& ok);
+ static void evaluateAggregateStep(void* dataPtr, QHash<QString, QVariant>& aggregateContext, QList<QVariant> argList);
+ static QVariant evaluateAggregateFinal(void* dataPtr, QHash<QString, QVariant>& 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<QString,QVariant> 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<QString,Db*> 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<Db*,int> 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<int,QueryResultsHandler> 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<QString, QVariant>& args, Flags flags);
+
+ /**
+ * @overload
+ */
+ SqlQueryPtr execListArg(const QString& query, const QList<QVariant>& 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<RegisteredFunction> 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 <sqlite.h>
+#include <QThread>
+#include <QPointer>
+#include <QDebug>
+
+/**
+ * @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 T>
+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<QString, QVariant>& 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<QVariant>& resultValues);
+ };
+
+ Query(AbstractDb2<T>* 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<QVariant>& args);
+ bool execInternal(const QHash<QString, QVariant>& 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<AbstractDb2<T>> db;
+ sqlite_vm* stmt = nullptr;
+ int errorCode = SQLITE_OK;
+ QString errorMessage;
+ int colCount = -1;
+ QStringList colNames;
+ QList<QVariant> 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<QVariant> 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<QString,QVariant> getAggregateContext(sqlite_func* func);
+ static void setAggregateContext(sqlite_func* func, const QHash<QString,QVariant>& aggregateContext);
+ static void releaseAggregateContext(sqlite_func* func);
+
+ sqlite* dbHandle = nullptr;
+ QString dbErrorMessage;
+ int dbErrorCode = SQLITE_OK;
+ QList<FunctionUserData*> userDataList;
+ QList<Query*> queries;
+};
+
+//------------------------------------------------------------------------------------
+// AbstractDb2
+//------------------------------------------------------------------------------------
+
+template <class T>
+AbstractDb2<T>::AbstractDb2(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) :
+ AbstractDb(name, path, connOptions)
+{
+}
+
+template <class T>
+AbstractDb2<T>::~AbstractDb2()
+{
+ if (isOpenInternal())
+ closeInternal();
+}
+
+template <class T>
+bool AbstractDb2<T>::isOpenInternal()
+{
+ return dbHandle != nullptr;
+}
+
+template <class T>
+SqlQueryPtr AbstractDb2<T>::prepare(const QString& query)
+{
+ return SqlQueryPtr(new Query(this, query));
+}
+
+template <class T>
+void AbstractDb2<T>::interruptExecution()
+{
+ if (!isOpenInternal())
+ return;
+
+ sqlite_interrupt(dbHandle);
+}
+
+template <class T>
+QString AbstractDb2<T>::getErrorTextInternal()
+{
+ return dbErrorMessage;
+}
+
+template <class T>
+int AbstractDb2<T>::getErrorCodeInternal()
+{
+ return dbErrorCode;
+}
+
+template <class T>
+bool AbstractDb2<T>::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 <class T>
+bool AbstractDb2<T>::closeInternal()
+{
+ resetError();
+ if (!dbHandle)
+ return false;
+
+ cleanUp();
+
+ sqlite_close(dbHandle);
+ dbHandle = nullptr;
+ return true;
+}
+
+template <class T>
+void AbstractDb2<T>::initAfterOpen()
+{
+}
+
+template <class T>
+QString AbstractDb2<T>::getTypeLabel()
+{
+ return T::label;
+}
+
+template <class T>
+bool AbstractDb2<T>::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<FunctionUserData*> it(userDataList);
+ while (it.hasNext())
+ {
+ userData = it.next();
+ if (userData->name == name && userData->argCount == argCount)
+ {
+ it.remove();
+ delete userData;
+ }
+ }
+
+ return true;
+}
+
+template <class T>
+bool AbstractDb2<T>::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<T>::evaluateScalar, userData);
+
+ return res == SQLITE_OK;
+}
+
+template <class T>
+bool AbstractDb2<T>::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<T>::evaluateAggregateStep,
+ &AbstractDb2<T>::evaluateAggregateFinal,
+ userData);
+
+ return res == SQLITE_OK;
+}
+
+template <class T>
+bool AbstractDb2<T>::registerCollationInternal(const QString& name)
+{
+ // Not supported in SQLite 2
+ UNUSED(name);
+ return false;
+}
+
+template <class T>
+bool AbstractDb2<T>::deregisterCollationInternal(const QString& name)
+{
+ // Not supported in SQLite 2
+ UNUSED(name);
+ return false;
+}
+
+template <class T>
+void AbstractDb2<T>::cleanUp()
+{
+ for (Query* q : queries)
+ q->finalize();
+}
+
+template <class T>
+void AbstractDb2<T>::resetError()
+{
+ dbErrorCode = 0;
+ dbErrorMessage = QString::null;
+}
+
+template <class T>
+void AbstractDb2<T>::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<QVariant> 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 <class T>
+QList<QVariant> AbstractDb2<T>::getArgs(int argCount, const char** args)
+{
+ QList<QVariant> results;
+
+ for (int i = 0; i < argCount; i++)
+ {
+ if (!args[i])
+ {
+ results << QVariant();
+ continue;
+ }
+
+ results << QString::fromUtf8(args[i]);
+ }
+ return results;
+}
+
+template <class T>
+void AbstractDb2<T>::evaluateScalar(sqlite_func* func, int argCount, const char** args)
+{
+ QList<QVariant> argList = getArgs(argCount, args);
+ bool ok = true;
+ QVariant result = AbstractDb::evaluateScalar(sqlite_user_data(func), argList, ok);
+ storeResult(func, result, ok);
+}
+
+template <class T>
+void AbstractDb2<T>::evaluateAggregateStep(sqlite_func* func, int argCount, const char** args)
+{
+ void* dataPtr = sqlite_user_data(func);
+ QList<QVariant> argList = getArgs(argCount, args);
+ QHash<QString,QVariant> aggregateContext = getAggregateContext(func);
+
+ AbstractDb::evaluateAggregateStep(dataPtr, aggregateContext, argList);
+
+ setAggregateContext(func, aggregateContext);
+}
+
+template <class T>
+void AbstractDb2<T>::evaluateAggregateFinal(sqlite_func* func)
+{
+ void* dataPtr = sqlite_user_data(func);
+ QHash<QString,QVariant> aggregateContext = getAggregateContext(func);
+
+ bool ok = true;
+ QVariant result = AbstractDb::evaluateAggregateFinal(dataPtr, aggregateContext, ok);
+
+ storeResult(func, result, ok);
+ releaseAggregateContext(func);
+}
+
+template <class T>
+void*AbstractDb2<T>::getContextMemPtr(sqlite_func* func)
+{
+ return sqlite_aggregate_context(func, sizeof(QHash<QString,QVariant>**));
+}
+
+template <class T>
+QHash<QString, QVariant> AbstractDb2<T>::getAggregateContext(sqlite_func* func)
+{
+ return AbstractDb::getAggregateContext(getContextMemPtr(func));
+}
+
+template <class T>
+void AbstractDb2<T>::setAggregateContext(sqlite_func* func, const QHash<QString, QVariant>& aggregateContext)
+{
+ AbstractDb::setAggregateContext(getContextMemPtr(func), aggregateContext);
+}
+
+template <class T>
+void AbstractDb2<T>::releaseAggregateContext(sqlite_func* func)
+{
+ AbstractDb::releaseAggregateContext(getContextMemPtr(func));
+}
+
+//------------------------------------------------------------------------------------
+// Query
+//------------------------------------------------------------------------------------
+
+template <class T>
+AbstractDb2<T>::Query::Query(AbstractDb2<T>* db, const QString& query) :
+ db(db)
+{
+ this->query = query;
+ db->queries << this;
+}
+
+template <class T>
+AbstractDb2<T>::Query::~Query()
+{
+ if (db.isNull())
+ return;
+
+ finalize();
+ db->queries.removeOne(this);
+}
+
+template <class T>
+void AbstractDb2<T>::Query::copyErrorFromDb()
+{
+ if (db->dbErrorCode != 0)
+ {
+ errorCode = db->dbErrorCode;
+ errorMessage = db->dbErrorMessage;
+ return;
+ }
+}
+
+template <class T>
+void AbstractDb2<T>::Query::copyErrorToDb()
+{
+ db->dbErrorCode = errorCode;
+ db->dbErrorMessage = errorMessage;
+}
+
+template <class T>
+void AbstractDb2<T>::Query::setError(int code, const QString& msg)
+{
+ if (errorCode != SQLITE_OK)
+ return; // don't overwrite first error
+
+ errorCode = code;
+ errorMessage = msg;
+ copyErrorToDb();
+}
+
+template <class T>
+int AbstractDb2<T>::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 <class T>
+int AbstractDb2<T>::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 <class T>
+bool AbstractDb2<T>::Query::execInternal(const QList<QVariant>& 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 <class T>
+bool AbstractDb2<T>::Query::execInternal(const QHash<QString, QVariant>& 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 <class T>
+QString AbstractDb2<T>::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 <class T>
+int AbstractDb2<T>::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 <class T>
+QString AbstractDb2<T>::Query::getErrorText()
+{
+ return errorMessage;
+}
+
+template <class T>
+int AbstractDb2<T>::Query::getErrorCode()
+{
+ return errorCode;
+}
+
+template <class T>
+QStringList AbstractDb2<T>::Query::getColumnNames()
+{
+ return colNames;
+}
+
+template <class T>
+int AbstractDb2<T>::Query::columnCount()
+{
+ return colCount;
+}
+
+template <class T>
+qint64 AbstractDb2<T>::Query::rowsAffected()
+{
+ return affected;
+}
+
+template <class T>
+SqlResultsRowPtr AbstractDb2<T>::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 <class T>
+bool AbstractDb2<T>::Query::hasNextInternal()
+{
+ return rowAvailable && stmt;
+}
+
+template <class T>
+int AbstractDb2<T>::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 <class T>
+bool AbstractDb2<T>::Query::checkDbState()
+{
+ if (db.isNull() || !db->dbHandle)
+ {
+ setError(SqlErrorCode::DB_NOT_DEFINED, "SqlQuery is no longer valid.");
+ return false;
+ }
+
+ return true;
+}
+
+template <class T>
+QString AbstractDb2<T>::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 <class T>
+int AbstractDb2<T>::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 <class T>
+void AbstractDb2<T>::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 <class T>
+void AbstractDb2<T>::Query::Row::init(const QStringList& columns, const QList<QVariant>& 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 <QThread>
+#include <QPointer>
+#include <QDebug>
+
+/**
+ * @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 T>
+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<QString, QVariant>& 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<T>* 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<QVariant>& args);
+ bool execInternal(const QHash<QString, QVariant>& 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<AbstractDb3<T>> 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<T>* 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<QVariant> 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<QString,QVariant> 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<QString,QVariant>& 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<Query*> 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 <class T>
+AbstractDb3<T>::AbstractDb3(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) :
+ AbstractDb(name, path, connOptions)
+{
+}
+
+template <class T>
+AbstractDb3<T>::~AbstractDb3()
+{
+ if (isOpenInternal())
+ closeInternal();
+}
+
+template <class T>
+bool AbstractDb3<T>::isOpenInternal()
+{
+ return dbHandle != nullptr;
+}
+
+template <class T>
+void AbstractDb3<T>::interruptExecution()
+{
+ if (!isOpenInternal())
+ return;
+
+ T::interrupt(dbHandle);
+}
+
+template <class T>
+QString AbstractDb3<T>::getErrorTextInternal()
+{
+ return dbErrorMessage;
+}
+
+template <class T>
+int AbstractDb3<T>::getErrorCodeInternal()
+{
+ return dbErrorCode;
+}
+
+template <class T>
+bool AbstractDb3<T>::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 <class T>
+bool AbstractDb3<T>::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 <class T>
+void AbstractDb3<T>::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 <class T>
+SqlQueryPtr AbstractDb3<T>::prepare(const QString& query)
+{
+ return SqlQueryPtr(new Query(this, query));
+}
+
+template <class T>
+QString AbstractDb3<T>::getTypeLabel()
+{
+ return T::label;
+}
+
+template <class T>
+bool AbstractDb3<T>::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 <class T>
+bool AbstractDb3<T>::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<T>::evaluateScalar,
+ nullptr,
+ nullptr,
+ &AbstractDb3<T>::deleteUserData);
+
+ return res == T::OK;
+}
+
+template <class T>
+bool AbstractDb3<T>::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<T>::evaluateAggregateStep,
+ &AbstractDb3<T>::evaluateAggregateFinal,
+ &AbstractDb3<T>::deleteUserData);
+
+ return res == T::OK;
+}
+
+template <class T>
+bool AbstractDb3<T>::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<T>::evaluateCollation,
+ &AbstractDb3<T>::deleteCollationUserData);
+ return res == T::OK;
+}
+
+template <class T>
+bool AbstractDb3<T>::deregisterCollationInternal(const QString& name)
+{
+ if (!dbHandle)
+ return false;
+
+ T::create_collation_v2(dbHandle, name.toUtf8().constData(), T::UTF8, nullptr, nullptr, nullptr);
+ return true;
+}
+
+template <class T>
+QString AbstractDb3<T>::extractLastError()
+{
+ dbErrorCode = T::extended_errcode(dbHandle);
+ dbErrorMessage = QString::fromUtf8(T::errmsg(dbHandle));
+ return dbErrorMessage;
+}
+
+template <class T>
+void AbstractDb3<T>::cleanUp()
+{
+ for (Query* q : queries)
+ q->finalize();
+
+ safe_delete(defaultCollationUserData);
+}
+
+template <class T>
+void AbstractDb3<T>::resetError()
+{
+ dbErrorCode = 0;
+ dbErrorMessage = QString::null;
+}
+
+template <class T>
+void AbstractDb3<T>::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<QVariant> 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 <class T>
+QList<QVariant> AbstractDb3<T>::getArgs(int argCount, typename T::value** args)
+{
+ int dataType;
+ QList<QVariant> 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<const char*>(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<const QChar*>(T::value_text16(args[i])),
+ T::value_bytes16(args[i]) / sizeof(QChar)
+ );
+ break;
+ }
+ results << value;
+ }
+ return results;
+}
+
+template <class T>
+void AbstractDb3<T>::evaluateScalar(typename T::context* context, int argCount, typename T::value** args)
+{
+ QList<QVariant> argList = getArgs(argCount, args);
+ bool ok = true;
+ QVariant result = AbstractDb::evaluateScalar(T::user_data(context), argList, ok);
+ storeResult(context, result, ok);
+}
+
+template <class T>
+void AbstractDb3<T>::evaluateAggregateStep(typename T::context* context, int argCount, typename T::value** args)
+{
+ void* dataPtr = T::user_data(context);
+ QList<QVariant> argList = getArgs(argCount, args);
+ QHash<QString,QVariant> aggregateContext = getAggregateContext(context);
+
+ AbstractDb::evaluateAggregateStep(dataPtr, aggregateContext, argList);
+
+ setAggregateContext(context, aggregateContext);
+}
+
+template <class T>
+void AbstractDb3<T>::evaluateAggregateFinal(typename T::context* context)
+{
+ void* dataPtr = T::user_data(context);
+ QHash<QString,QVariant> aggregateContext = getAggregateContext(context);
+
+ bool ok = true;
+ QVariant result = AbstractDb::evaluateAggregateFinal(dataPtr, aggregateContext, ok);
+
+ storeResult(context, result, ok);
+ releaseAggregateContext(context);
+}
+
+template <class T>
+int AbstractDb3<T>::evaluateCollation(void* userData, int length1, const void* value1, int length2, const void* value2)
+{
+ UNUSED(length1);
+ UNUSED(length2);
+ CollationUserData* collUserData = reinterpret_cast<CollationUserData*>(userData);
+ return COLLATIONS->evaluate(collUserData->name, QString::fromUtf8((const char*)value1), QString::fromUtf8((const char*)value2));
+}
+
+template <class T>
+void AbstractDb3<T>::deleteCollationUserData(void* userData)
+{
+ if (!userData)
+ return;
+
+ CollationUserData* collUserData = reinterpret_cast<CollationUserData*>(userData);
+ delete collUserData;
+}
+
+template <class T>
+void AbstractDb3<T>::deleteUserData(void* dataPtr)
+{
+ if (!dataPtr)
+ return;
+
+ FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr);
+ delete userData;
+}
+
+template <class T>
+void* AbstractDb3<T>::getContextMemPtr(typename T::context* context)
+{
+ return T::aggregate_context(context, sizeof(QHash<QString,QVariant>**));
+}
+
+template <class T>
+QHash<QString, QVariant> AbstractDb3<T>::getAggregateContext(typename T::context* context)
+{
+ return AbstractDb::getAggregateContext(getContextMemPtr(context));
+}
+
+template <class T>
+void AbstractDb3<T>::setAggregateContext(typename T::context* context, const QHash<QString, QVariant>& aggregateContext)
+{
+ AbstractDb::setAggregateContext(getContextMemPtr(context), aggregateContext);
+}
+
+template <class T>
+void AbstractDb3<T>::releaseAggregateContext(typename T::context* context)
+{
+ AbstractDb::releaseAggregateContext(getContextMemPtr(context));
+}
+
+template <class T>
+void AbstractDb3<T>::registerDefaultCollation(void* fnUserData, typename T::handle* fnDbHandle, int eTextRep, const char* collationName)
+{
+ CollationUserData* defUserData = reinterpret_cast<CollationUserData*>(fnUserData);
+ if (!defUserData)
+ {
+ qWarning() << "Null userData in AbstractDb3<T>::registerDefaultCollation().";
+ return;
+ }
+
+ AbstractDb3<T>* db = defUserData->db;
+ if (!db)
+ {
+ qWarning() << "No database defined in userData of AbstractDb3<T>::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<T>::registerDefaultCollation().";
+ return;
+ }
+
+ int res = T::create_collation_v2(fnDbHandle, collationName, eTextRep, nullptr,
+ &AbstractDb3<T>::evaluateDefaultCollation, nullptr);
+
+ if (res != T::OK)
+ qWarning() << "Could not register default collation in AbstractDb3<T>::registerDefaultCollation().";
+}
+
+template <class T>
+int AbstractDb3<T>::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 <class T>
+void AbstractDb3<T>::registerDefaultCollationRequestHandler()
+{
+ if (!dbHandle)
+ return;
+
+ defaultCollationUserData = new CollationUserData;
+ defaultCollationUserData->db = this;
+
+ int res = T::collation_needed(dbHandle, defaultCollationUserData, &AbstractDb3<T>::registerDefaultCollation);
+ if (res != T::OK)
+ qWarning() << "Could not register default collation request handler. Unknown collations will cause errors.";
+}
+
+//------------------------------------------------------------------------------------
+// Results
+//------------------------------------------------------------------------------------
+
+template <class T>
+AbstractDb3<T>::Query::Query(AbstractDb3<T>* db, const QString& query) :
+ db(db)
+{
+ this->query = query;
+ db->queries << this;
+}
+
+template <class T>
+AbstractDb3<T>::Query::~Query()
+{
+ if (db.isNull())
+ return;
+
+ finalize();
+ db->queries.removeOne(this);
+}
+
+template <class T>
+void AbstractDb3<T>::Query::copyErrorFromDb()
+{
+ if (db->dbErrorCode != 0)
+ {
+ errorCode = db->dbErrorCode;
+ errorMessage = db->dbErrorMessage;
+ return;
+ }
+}
+
+template <class T>
+void AbstractDb3<T>::Query::copyErrorToDb()
+{
+ db->dbErrorCode = errorCode;
+ db->dbErrorMessage = errorMessage;
+}
+
+template <class T>
+void AbstractDb3<T>::Query::setError(int code, const QString& msg)
+{
+ if (errorCode != T::OK)
+ return; // don't overwrite first error
+
+ errorCode = code;
+ errorMessage = msg;
+ copyErrorToDb();
+}
+
+template <class T>
+int AbstractDb3<T>::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 <class T>
+int AbstractDb3<T>::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 <class T>
+bool AbstractDb3<T>::Query::execInternal(const QList<QVariant>& 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 <class T>
+bool AbstractDb3<T>::Query::execInternal(const QHash<QString, QVariant>& 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 <class T>
+int AbstractDb3<T>::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 <class T>
+bool AbstractDb3<T>::Query::checkDbState()
+{
+ if (db.isNull() || !db->dbHandle)
+ {
+ setError(SqlErrorCode::DB_NOT_DEFINED, "SqlQuery is no longer valid.");
+ return false;
+ }
+
+ return true;
+}
+
+template <class T>
+void AbstractDb3<T>::Query::finalize()
+{
+ if (stmt)
+ {
+ T::finalize(stmt);
+ stmt = nullptr;
+ }
+}
+
+template <class T>
+QString AbstractDb3<T>::Query::getErrorText()
+{
+ return errorMessage;
+}
+
+template <class T>
+int AbstractDb3<T>::Query::getErrorCode()
+{
+ return errorCode;
+}
+
+template <class T>
+QStringList AbstractDb3<T>::Query::getColumnNames()
+{
+ return colNames;
+}
+
+template <class T>
+int AbstractDb3<T>::Query::columnCount()
+{
+ return colCount;
+}
+
+template <class T>
+qint64 AbstractDb3<T>::Query::rowsAffected()
+{
+ return affected;
+}
+
+template <class T>
+SqlResultsRowPtr AbstractDb3<T>::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 <class T>
+bool AbstractDb3<T>::Query::hasNextInternal()
+{
+ return rowAvailable && stmt && checkDbState();
+}
+
+template <class T>
+int AbstractDb3<T>::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 <class T>
+int AbstractDb3<T>::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 <class T>
+int AbstractDb3<T>::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 <class T>
+int AbstractDb3<T>::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<const char*>(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<const QChar*>(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 <QDebug>
+
+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 <QVariant>
+#include <QHash>
+#include <QRunnable>
+#include <QString>
+#include <QPointer>
+#include <QByteArray>
+
+/**
+ * @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<QVariant> or QHash<QString,QVariant>. 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 <QSharedPointer>
+
+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<GuardedAttach> 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 <QDebug>
+
+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<bool> ChainExecutor::getMandatoryQueries() const
+{
+ return mandatoryQueries;
+}
+
+void ChainExecutor::setMandatoryQueries(const QList<bool>& 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 &paramName, 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::ExecutionError>& 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 <QObject>
+
+// 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<int,QString> 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<bool> 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<bool>& 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<ExecutionError>& 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<bool> 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<ExecutionError> 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<QString,QVariant> 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 <QMetaEnum>
+
+Db::Db()
+{
+}
+
+Db::~Db()
+{
+}
+
+void Db::metaInit()
+{
+ qRegisterMetaType<Db*>("Db*");
+ qRegisterMetaTypeStreamOperators<Db*>("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<int>(flags));
+}
+
+QDataStream &operator <<(QDataStream &out, const Db* myObj)
+{
+ out << reinterpret_cast<quint64>(myObj);
+ return out;
+}
+
+
+QDataStream &operator >>(QDataStream &in, Db*& myObj)
+{
+ quint64 ptr;
+ in >> ptr;
+ myObj = reinterpret_cast<Db*>(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 <QObject>
+#include <QVariant>
+#include <QList>
+#include <QHash>
+#include <QReadWriteLock>
+#include <QRunnable>
+#include <QStringList>
+#include <QSet>
+
+/** @file */
+
+class AsyncQueryRunner;
+class Db;
+class DbManager;
+class SqlQuery;
+
+typedef QSharedPointer<SqlQuery> 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<int> 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<int> 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<QString,QVariant> 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<void(SqlQueryPtr)> 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: <tt>PRAGMA encoding;</tt>
+ *
+ * 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<QString,QVariant>& 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<QString,QVariant>& 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<QVariant> &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<QString, QVariant>& 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<QVariant> argList) = 0;
+
+ /**
+ * @brief Executes SQL query.
+ * @overload
+ */
+ virtual SqlQueryPtr exec(const QString &query, std::initializer_list<std::pair<QString,QVariant>> 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<QVariant>& 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<QString, QVariant>& 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<QVariant>& 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<QString, QVariant>& 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 <tt>ATTACH 'name'</tt> 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 <tt>DETACH</tt> 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 <tt>ATTACH</tt> 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<Db*,QString>& 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 <tt>ATTACH</tt> query execution.
+ */
+ virtual QSet<QString> 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<int> 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 <QString>
+#include <QVariant>
+
+/**
+ * @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:
+ * <ul>
+ * <li>single QLabel with text set to DbPluginOption::label,</li>
+ * <li>an input widget, that depends on DbPluginOption::type.</li>
+ * </ul>
+ *
+ * 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 <QObject>
+#include <QSqlDatabase>
+#include <QVariant>
+#include <QList>
+#include <QMap>
+#include <QHash>
+#include <QMutex>
+#include <QRunnable>
+
+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<QVariant>& args);
+ quint32 asyncExecArgs(const QString& query, const QMap<QString,QVariant>& 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<QVariant>& args,
+ bool singleCell);
+ SqlResultsPtr execInternal(const QString& query, const QMap<QString,QVariant>& 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<QString, QVariant>& connOptions) :
+ AbstractDb3(name, path, connOptions)
+{
+}
+
+DbSqlite3::DbSqlite3(const QString& name, const QString& path) :
+ DbSqlite3(name, path, QHash<QString,QVariant>())
+{
+}
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 <sqlite3.h>
+
+STD_SQLITE3_DRIVER(Sqlite3, "SQLite 3",,)
+
+class API_EXPORT DbSqlite3 : public AbstractDb3<Sqlite3>
+{
+ 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<QString, QVariant>& 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 <QSet>
+
+InvalidDb::InvalidDb(const QString& name, const QString& path, const QHash<QString, QVariant>& 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<QString, QVariant>& 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<QString, QVariant>& value)
+{
+ connOptions = value;
+}
+
+void InvalidDb::setTimeout(int secs)
+{
+ timeout = secs;
+}
+
+int InvalidDb::getTimeout() const
+{
+ return timeout;
+}
+
+SqlQueryPtr InvalidDb::exec(const QString& query, const QList<QVariant>& args, Db::Flags flags)
+{
+ UNUSED(query);
+ UNUSED(args);
+ UNUSED(flags);
+ return SqlQueryPtr();
+}
+
+SqlQueryPtr InvalidDb::exec(const QString& query, const QHash<QString, QVariant>& 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<QVariant> argList)
+{
+ UNUSED(query);
+ UNUSED(argList);
+ return SqlQueryPtr();
+}
+
+SqlQueryPtr InvalidDb::exec(const QString& query, std::initializer_list<std::pair<QString, QVariant> > argMap)
+{
+ UNUSED(query);
+ UNUSED(argMap);
+ return SqlQueryPtr();
+}
+
+void InvalidDb::asyncExec(const QString& query, const QList<QVariant>& args, Db::QueryResultsHandler resultsHandler, Db::Flags flags)
+{
+ UNUSED(query);
+ UNUSED(args);
+ UNUSED(resultsHandler);
+ UNUSED(flags);
+}
+
+void InvalidDb::asyncExec(const QString& query, const QHash<QString, QVariant>& 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<QVariant>& args, Db::Flags flags)
+{
+ UNUSED(query);
+ UNUSED(args);
+ UNUSED(flags);
+ return 0;
+}
+
+quint32 InvalidDb::asyncExec(const QString& query, const QHash<QString, QVariant>& 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<Db*, QString>& InvalidDb::getAttachedDatabases()
+{
+ return attachedDbs;
+}
+
+QSet<QString> InvalidDb::getAllAttaches()
+{
+ return QSet<QString>();
+}
+
+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<QString, QVariant>& connOptions);
+
+ bool isOpen();
+ QString getName();
+ QString getPath();
+ quint8 getVersion();
+ Dialect getDialect();
+ QString getEncoding();
+ QHash<QString, QVariant>& getConnectionOptions();
+ void setName(const QString& value);
+ void setPath(const QString& value);
+ void setConnectionOptions(const QHash<QString, QVariant>& value);
+ void setTimeout(int secs);
+ int getTimeout() const;
+ SqlQueryPtr exec(const QString& query, const QList<QVariant>& args, Flags flags);
+ SqlQueryPtr exec(const QString& query, const QHash<QString, QVariant>& 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<QVariant> argList);
+ SqlQueryPtr exec(const QString& query, std::initializer_list<std::pair<QString, QVariant> > argMap);
+ void asyncExec(const QString& query, const QList<QVariant>& args, QueryResultsHandler resultsHandler, Flags flags);
+ void asyncExec(const QString& query, const QHash<QString, QVariant>& args, QueryResultsHandler resultsHandler, Flags flags);
+ void asyncExec(const QString& query, QueryResultsHandler resultsHandler, Flags flags);
+ quint32 asyncExec(const QString& query, const QList<QVariant>& args, Flags flags);
+ quint32 asyncExec(const QString& query, const QHash<QString, QVariant>& 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<Db*, QString>& getAttachedDatabases();
+ QSet<QString> 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<QString, QVariant> connOptions;
+ int timeout = 0;
+ QHash<Db*, QString> 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 <QMutexLocker>
+#include <QDateTime>
+#include <QThreadPool>
+#include <QDebug>
+#include <schemaresolver.h>
+#include <parser/lexer.h>
+#include <common/table.h>
+#include <QtMath>
+
+// 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::SourceTablePtr> QueryExecutor::getSourceTables() const
+{
+ return context->sourceTables;
+}
+
+int QueryExecutor::getTotalPages() const
+{
+ return context->totalPages;
+}
+
+QList<QueryExecutor::ResultColumnPtr> QueryExecutor::getResultColumns() const
+{
+ return context->resultColumns;
+}
+
+QList<QueryExecutor::ResultRowIdColumnPtr> 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::EditionForbiddenReason> 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<DataType> QueryExecutor::resolveColumnTypes(Db* db, QList<QueryExecutor::ResultColumnPtr>& columns, bool noDbLocking)
+{
+ QSet<Table> tables;
+ for (ResultColumnPtr col : columns)
+ tables << Table(col->database, col->table);
+
+ SchemaResolver resolver(db);
+ resolver.setNoDbLocking(noDbLocking);
+
+ QHash<Table,SqliteCreateTablePtr> parsedTables;
+ SqliteCreateTablePtr createTable;
+ for (const Table& t : tables)
+ {
+ createTable = resolver.getParsedObject(t.getDatabase(), t.getTable(), SchemaResolver::TABLE).dynamicCast<SqliteCreateTable>();
+ if (!createTable)
+ {
+ qWarning() << "Could not resolve columns of table" << t.getTable() << "while quering datatypes for queryexecutor columns.";
+ continue;
+ }
+ parsedTables[t] = createTable;
+ }
+
+ QList<DataType> 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<int>(reason);
+}
+
+int qHash(QueryExecutor::ColumnEditionForbiddenReason reason)
+{
+ return static_cast<int>(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 <QObject>
+#include <QHash>
+#include <QMutex>
+#include <QRunnable>
+
+/** @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:
+ * <ul>
+ * <li>programatically define sorting on desired column.</li>
+ * <li>define result rows paging (page size and queried page)</li>
+ * <li>refer other databases by their symbolic name and they will be attached and detached on the fly</li>
+ * <li>define maximum cell data size (in bytes), so you won't read too much data at once</li>
+ * </ul>
+ *
+ * Total number of result rows is counted by a separate call to the database (using <tt>SELECT count(*) ...</tt>)
+ * 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<Sort> 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<ColumnEditionForbiddenReason> 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<ResultColumn> 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<QString,QString> queryExecutorAliasToColumn;
+ };
+
+ /**
+ * @brief Shared pointer to ResultRowIdColumn.
+ */
+ typedef QSharedPointer<ResultRowIdColumn> 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<SourceTable> 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:
+ * <ul>
+ * <li>Modify tokens</li> - modify tokens of top level objects in parsedQueries
+ * and call QueryExecutorStep::updateQueries().
+ * <li>Modify parsed objects</li> - modify logical structure and values of
+ * objects in parsedQueries, then call on those objects SqliteStatement::rebuildTokens()
+ * and finally call QueryExecutorStep::updateQueries.
+ * </ul>
+ *
+ * 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<QString,QVariant> 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<SqliteQueryPtr> 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<EditionForbiddenReason> 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<ResultRowIdColumnPtr> 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<ResultColumnPtr> resultColumns;
+
+ /**
+ * @brief Data source tables mentioned in the query.
+ *
+ * List of tables used as data source in the query.
+ */
+ QSet<SourceTablePtr> 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<QueryExecutor::SourceTablePtr> 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<QueryExecutor::ResultColumnPtr> 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<QueryExecutor::ResultRowIdColumnPtr> 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<EditionForbiddenReason> 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<DataType> resolveColumnTypes(Db* db, QList<ResultColumnPtr>& 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<QueryExecutorStep*> 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 <QDebug>
+
+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<SelectResolver::Table,QHash<QString,QString>> QueryExecutorAddRowIds::addRowIdForTables(SqliteSelect* select, bool& ok, bool isTopSelect)
+{
+ QHash<SelectResolver::Table,QHash<QString,QString>> 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<SelectResolver::Table> 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<SqliteSelect*> QueryExecutorAddRowIds::getSubSelects(SqliteSelect::Core* core)
+{
+ QList<SqliteSelect*> 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<QString,QString> QueryExecutorAddRowIds::getNextColNames(const SelectResolver::Table& table)
+{
+ QHash<QString,QString> colNames;
+
+ SchemaResolver resolver(db);
+ SqliteQueryPtr query = resolver.getParsedObject(table.database, table.table, SchemaResolver::TABLE);
+ SqliteCreateTablePtr createTable = query.dynamicCast<SqliteCreateTable>();
+ 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<SqliteCreateTable::Column::Constraint*>(primaryKey);
+ if (columnConstr)
+ {
+ colNames[getNextColName()] = dynamic_cast<SqliteCreateTable::Column*>(columnConstr->parentStatement())->name;
+ return colNames;
+ }
+
+ SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(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<SelectResolver::Table,QHash<QString,QString>>& rowIdColsMap, bool isTopSelect)
+{
+ QHash<QString, QString> 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<QString,QString> 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<SelectResolver::Table, QHash<QString, QString> >& 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<SelectResolver::Table,QHash<QString,QString>> 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<SqliteSelect*> 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<QString, QString> 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 <QScopedPointer>
+
+bool QueryExecutorAttaches::exec()
+{
+ QScopedPointer<DbAttacher> 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 <QObject>
+
+/**
+ * @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 <QDebug>
+
+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 <QDebug>
+
+// 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<SelectResolver::Column> 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 <math.h>
+#include <QDebug>
+
+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<SelectResolver::Table> 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 <QDateTime>
+#include <QDebug>
+#include <schemaresolver.h>
+
+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<QString, QVariant> 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<QString, QVariant> QueryExecutorExecute::getBindParamsForQuery(SqliteQueryPtr query)
+{
+ QHash<QString, QVariant> 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<Sqlite2ColumnDataTypeHelper*>(results.data());
+ if (!sqlite2Helper)
+ return;
+
+ QPair<QString,QString> key;
+ SqliteCreateTablePtr createTable;
+
+ SchemaResolver resolver(db);
+ QHash<QPair<QString,QString>,SqliteCreateTablePtr> tables;
+ for (QueryExecutor::SourceTablePtr tab : context->sourceTables)
+ {
+ if (tab->table.isNull())
+ continue;
+
+ key = QPair<QString,QString>(tab->database, tab->table);
+ createTable = resolver.getParsedObject(tab->database, tab->table, SchemaResolver::TABLE).dynamicCast<SqliteCreateTable>();
+ tables[key] = createTable;
+ }
+
+ sqlite2Helper->clearBinaryTypes();
+
+ SqliteCreateTable::Column* column = nullptr;
+ int idx = -1 + context->rowIdColumns.size();
+ for (QueryExecutor::ResultColumnPtr resCol : context->resultColumns)
+ {
+ idx++;
+ key = QPair<QString,QString>(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 <QHash>
+
+/**
+ * @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<QString, QVariant> 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 <QDebug>
+
+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 <QDebug>
+
+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 <QDebug>
+
+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 <QDebug>
+
+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<SqliteCreateView>();
+ 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<SqliteSelect::Core::SingleSource*> sources = core->getAllTypedStatements<SqliteSelect::Core::SingleSource>();
+ 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<QString,QStringList> 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<View,SqliteCreateViewPtr> 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<SqliteSelect>();
+}
+
+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 <QObject>
+
+/**
+ * @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<QVariant>& args)
+{
+ UNUSED(args);
+ return false;
+}
+
+bool SqlErrorResults::execInternal(const QHash<QString, QVariant>& 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 <QStringList>
+
+/**
+ * @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<QVariant>& args);
+ bool execInternal(const QHash<QString, QVariant>& 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<SqlResultsRowPtr> SqlQuery::getAll()
+{
+ if (!preloaded)
+ preload();
+
+ return preloadedData;
+}
+
+void SqlQuery::preload()
+{
+ if (preloaded)
+ return;
+
+ QList<SqlResultsRowPtr> 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<QVariant>& args)
+{
+ queryArgs = args;
+}
+
+void SqlQuery::setArgs(const QHash<QString, QVariant>& args)
+{
+ queryArgs = args;
+}
+
+
+void RowIdConditionBuilder::setRowId(const RowId& rowId)
+{
+ static const QString argTempalate = QStringLiteral(":rowIdArg%1");
+
+ QString arg;
+ QHashIterator<QString,QVariant> it(rowId);
+ int i = 0;
+ while (it.hasNext())
+ {
+ it.next();
+ arg = argTempalate.arg(i++);
+ queryArgs[arg] = it.value();
+ conditions << it.key() + " = " + arg;
+ }
+}
+
+const QHash<QString, QVariant>& 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 <QList>
+#include <QSharedPointer>
+
+/** @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: <tt>ROWID -> some_integer</tt>
+ */
+typedef QHash<QString,QVariant> 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<SqlResultsRowPtr> 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 <class T>
+ QList<T> columnAsList(const QString& name)
+ {
+ QList<T> list;
+ SqlResultsRowPtr row;
+ while (hasNext())
+ {
+ row = next();
+ list << row->value(name).value<T>();
+ }
+ 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 <class T>
+ QList<T> columnAsList(int index)
+ {
+ QList<T> list;
+ if (index < 0 || index >= columnCount())
+ return list;
+
+ SqlResultsRowPtr row;
+ while (hasNext())
+ {
+ row = next();
+ list << row->value(index).value<T>();
+ }
+ return list;
+ }
+
+ QString getQuery() const;
+ void setFlags(Db::Flags flags);
+ void clearArgs();
+ void setArgs(const QList<QVariant>& args);
+ void setArgs(const QHash<QString,QVariant>& 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<QVariant>& args) = 0;
+ virtual bool execInternal(const QHash<QString, QVariant>& 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<SqlResultsRowPtr> preloadedData;
+
+ int affected = 0;
+
+ QString query;
+ QVariant queryArgs;
+ Db::Flags flags;
+};
+
+class API_EXPORT RowIdConditionBuilder
+{
+ public:
+ void setRowId(const RowId& rowId);
+ const QHash<QString,QVariant>& getQueryArgs();
+ QString build();
+
+ protected:
+ QStringList conditions;
+ QHash<QString,QVariant> 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<SqlQuery> 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<QString, QVariant> &SqlResultsRow::valueMap() const
+{
+ return valuesMap;
+}
+
+const QList<QVariant>& 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 <QVariant>
+#include <QList>
+#include <QHash>
+#include <QSharedPointer>
+
+/** @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<QString, QVariant>& 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<QVariant>& 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<QString,QVariant> 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<QVariant> values;
+};
+
+/**
+ * @brief Shared pointer to SQL query results row.
+ */
+typedef QSharedPointer<SqlResultsRow> 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 <QList>
+
+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<SqliteQueryPtr>& 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 <QDebug>
+#include <QThreadPool>
+
+DbObjectOrganizer::DbObjectOrganizer()
+{
+ // Default organizaer denies any referenced objects
+ confirmFunction = [](const QStringList&) -> bool {return false;};
+ nameConflictResolveFunction = [](QString&) -> bool {return false;};
+ conversionConfimFunction = [](const QList<QPair<QString,QString>>&) -> bool {return false;};
+ conversionErrorsConfimFunction = [](const QHash<QString,QSet<QString>>&) -> 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<QString>& 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<SqliteQueryPtr> allParsedObjects = srcResolver->getAllParsedObjects();
+ StrHash<SchemaResolver::ObjectDetails> 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<QString> 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<SqliteCreateTable>();
+ 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<Sqlite2ColumnDataTypeHelper*>(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<QString> DbObjectOrganizer::resolveReferencedTables(const QString& table, const QList<SqliteCreateTablePtr>& parsedTables)
+{
+ QSet<QString> 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<SchemaResolver::ObjectDetails>& details)
+{
+ if (srcDb->getVersion() == dstDb->getVersion())
+ return;
+
+
+ int dstVersion = dstDb->getVersion();
+ QSet<QString> 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<SqliteQueryPtr>& allParsedObjects)
+{
+ QList<SqliteCreateTablePtr> parsedTables;
+ SqliteCreateTablePtr parsedTable;
+ for (SqliteQueryPtr query : allParsedObjects.values())
+ {
+ parsedTable = query.dynamicCast<SqliteCreateTable>();
+ if (parsedTable)
+ parsedTables << parsedTable;
+ }
+
+ QSet<QString> 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<SqliteQueryPtr>& 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<SqliteCreateTable>();
+ 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 <QString>
+#include <QObject>
+#include <QRunnable>
+#include <QMutex>
+#include <QStringList>
+#include <QHash>
+
+class Db;
+class DbVersionConverter;
+
+class API_EXPORT DbObjectOrganizer : public QObject, public QRunnable, public Interruptable
+{
+ Q_OBJECT
+
+ public:
+ typedef std::function<bool(const QStringList& tables)> ReferencedTablesConfimFunction;
+ typedef std::function<bool(QString& nameInConflict)> NameConflictResolveFunction;
+ typedef std::function<bool(const QList<QPair<QString,QString>>& diffs)> ConversionConfimFunction;
+ typedef std::function<bool(const QHash<QString,QSet<QString>>& 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<QString>& 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<QString> resolveReferencedTables(const QString& table, const QList<SqliteCreateTablePtr>& parsedTables);
+ void collectDiffs(const StrHash<SchemaResolver::ObjectDetails>& details);
+ QString convertDdlToDstVersion(const QString& ddl);
+ void collectReferencedTables(const QString& table, const StrHash<SqliteQueryPtr>& allParsedObjects);
+ void collectReferencedIndexes(const QString& table);
+ void collectReferencedTriggersForTable(const QString& table);
+ void collectReferencedTriggersForView(const QString& view);
+ void findBinaryColumns(const QString& table, const StrHash<SqliteQueryPtr>& 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<QString> srcNames;
+ QSet<QString> srcTables;
+ QSet<QString> srcViews;
+ QSet<QString> srcIndexes;
+ QSet<QString> srcTriggers;
+ QHash<QString,QString> renamed;
+ QString srcTable;
+ QHash<QString,QStringList> binaryColumns; // hints for SQLite 2 databases
+ bool includeData = false;
+ bool includeIndexes = false;
+ bool includeTriggers = false;
+ bool deleteSourceObjects = false;
+ QSet<QString> referencedTables;
+ QHash<QString,QSet<QString>> errorsToConfirm;
+ QList<QPair<QString, QString>> 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 <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QtConcurrent/QtConcurrentRun>
+
+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<SqliteAttach>(query);
+ break;
+ case SqliteQueryType::BeginTrans:
+ newQuery = copyQuery<SqliteBeginTrans>(query);
+ break;
+ case SqliteQueryType::CommitTrans:
+ newQuery = copyQuery<SqliteCommitTrans>(query);
+ break;
+ case SqliteQueryType::Copy:
+ qWarning() << "COPY query passed to DbVersionConverter::convertToVersion2(). SQLite3 query should not have COPY statement.";
+ newQuery = copyQuery<SqliteCopy>(query);
+ break;
+ case SqliteQueryType::CreateIndex:
+ newQuery = copyQuery<SqliteCreateIndex>(query);
+ if (!modifyCreateIndexForVersion2(newQuery.dynamicCast<SqliteCreateIndex>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ case SqliteQueryType::CreateTable:
+ newQuery = copyQuery<SqliteCreateTable>(query);
+ if (!modifyCreateTableForVersion2(newQuery.dynamicCast<SqliteCreateTable>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ case SqliteQueryType::CreateTrigger:
+ newQuery = copyQuery<SqliteCreateTrigger>(query);
+ if (!modifyCreateTriggerForVersion2(newQuery.dynamicCast<SqliteCreateTrigger>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ case SqliteQueryType::CreateView:
+ newQuery = copyQuery<SqliteCreateView>(query);
+ if (!modifyCreateViewForVersion2(newQuery.dynamicCast<SqliteCreateView>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ case SqliteQueryType::CreateVirtualTable:
+ newQuery = copyQuery<SqliteCreateVirtualTable>(query);
+ if (!modifyVirtualTableForVesion2(newQuery, newQuery.dynamicCast<SqliteCreateVirtualTable>().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<SqliteDelete>(query);
+ if (!modifyDeleteForVersion2(newQuery.dynamicCast<SqliteDelete>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ case SqliteQueryType::Detach:
+ newQuery = copyQuery<SqliteDetach>(query);
+ break;
+ case SqliteQueryType::DropIndex:
+ newQuery = copyQuery<SqliteDropIndex>(query);
+ break;
+ case SqliteQueryType::DropTable:
+ newQuery = copyQuery<SqliteDropTable>(query);
+ break;
+ case SqliteQueryType::DropTrigger:
+ newQuery = copyQuery<SqliteDropTrigger>(query);
+ break;
+ case SqliteQueryType::DropView:
+ newQuery = copyQuery<SqliteDropView>(query);
+ break;
+ case SqliteQueryType::Insert:
+ newQuery = copyQuery<SqliteInsert>(query);
+ if (!modifyInsertForVersion2(newQuery.dynamicCast<SqliteInsert>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ case SqliteQueryType::Pragma:
+ newQuery = copyQuery<SqlitePragma>(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<SqliteRollback>(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<SqliteSelect>(query);
+ if (!modifySelectForVersion2(newQuery.dynamicCast<SqliteSelect>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ }
+ case SqliteQueryType::Update:
+ newQuery = copyQuery<SqliteUpdate>(query);
+ if (!modifyUpdateForVersion2(newQuery.dynamicCast<SqliteUpdate>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ case SqliteQueryType::Vacuum:
+ newQuery = copyQuery<SqliteVacuum>(query);
+ break;
+ case SqliteQueryType::UNDEFINED:
+ qWarning() << "UNDEFINED query type passed to DbVersionConverter::convertToVersion2().";
+ newQuery = SqliteEmptyQueryPtr::create();
+ break;
+ case SqliteQueryType::EMPTY:
+ newQuery = copyQuery<SqliteEmptyQuery>(query);
+ break;
+ }
+
+ if (!newQuery)
+ {
+ qCritical() << "Query type not matched in DbVersionConverter::convertToVersion2():" << static_cast<int>(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<SqliteAlterTable>(query);
+ qWarning() << "ALTER TABLE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have ALTER TABLE statement.";
+ break;
+ case SqliteQueryType::Analyze:
+ newQuery = copyQuery<SqliteAnalyze>(query);
+ qWarning() << "ANALYZE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have ANALYZE statement.";
+ break;
+ case SqliteQueryType::Attach:
+ newQuery = copyQuery<SqliteAttach>(query);
+ break;
+ case SqliteQueryType::BeginTrans:
+ newQuery = copyQuery<SqliteBeginTrans>(query);
+ if (!modifyBeginTransForVersion3(newQuery.dynamicCast<SqliteBeginTrans>().data()))
+ {
+ newQuery = SqliteEmptyQueryPtr::create();
+ storeErrorDiff(query.data());
+ }
+ break;
+ case SqliteQueryType::CommitTrans:
+ newQuery = copyQuery<SqliteCommitTrans>(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<SqliteCreateIndex>(query);
+ break;
+ case SqliteQueryType::CreateTable:
+ newQuery = copyQuery<SqliteCreateTable>(query);
+ break;
+ case SqliteQueryType::CreateTrigger:
+ newQuery = copyQuery<SqliteCreateTrigger>(query);
+ break;
+ case SqliteQueryType::CreateView:
+ newQuery = copyQuery<SqliteCreateView>(query);
+ break;
+ case SqliteQueryType::CreateVirtualTable:
+ newQuery = copyQuery<SqliteCreateVirtualTable>(query);
+ break;
+ case SqliteQueryType::Delete:
+ newQuery = copyQuery<SqliteDelete>(query);
+ break;
+ case SqliteQueryType::Detach:
+ newQuery = copyQuery<SqliteDetach>(query);
+ break;
+ case SqliteQueryType::DropIndex:
+ newQuery = copyQuery<SqliteDropIndex>(query);
+ break;
+ case SqliteQueryType::DropTable:
+ newQuery = copyQuery<SqliteDropTable>(query);
+ break;
+ case SqliteQueryType::DropTrigger:
+ newQuery = copyQuery<SqliteDropTrigger>(query);
+ break;
+ case SqliteQueryType::DropView:
+ newQuery = copyQuery<SqliteDropView>(query);
+ break;
+ case SqliteQueryType::Insert:
+ newQuery = copyQuery<SqliteInsert>(query);
+ break;
+ case SqliteQueryType::Pragma:
+ newQuery = copyQuery<SqlitePragma>(query);
+ break;
+ case SqliteQueryType::Reindex:
+ newQuery = copyQuery<SqliteReindex>(query);
+ qWarning() << "REINDEX query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have REINDEX statement.";
+ break;
+ case SqliteQueryType::Release:
+ newQuery = copyQuery<SqliteRelease>(query);
+ qWarning() << "RELEASE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have RELEASE statement.";
+ break;
+ case SqliteQueryType::Rollback:
+ newQuery = copyQuery<SqliteRollback>(query);
+ break;
+ case SqliteQueryType::Savepoint:
+ newQuery = copyQuery<SqliteSavepoint>(query);
+ qWarning() << "SAVEPOINT query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have SAVEPOINT statement.";
+ break;
+ case SqliteQueryType::Select:
+ newQuery = copyQuery<SqliteSelect>(query);
+ break;
+ case SqliteQueryType::Update:
+ newQuery = copyQuery<SqliteUpdate>(query);
+ break;
+ case SqliteQueryType::Vacuum:
+ newQuery = copyQuery<SqliteVacuum>(query);
+ break;
+ case SqliteQueryType::UNDEFINED:
+ qWarning() << "UNDEFINED query type passed to DbVersionConverter::convertToVersion3().";
+ case SqliteQueryType::EMPTY:
+ newQuery = copyQuery<SqliteEmptyQuery>(query);
+ break;
+ }
+
+ if (!newQuery)
+ {
+ qCritical() << "Query type not matched in DbVersionConverter::convertToVersion3():" << static_cast<int>(query->queryType);
+ return SqliteQueryPtr();
+ }
+
+ if (newQuery->queryType != SqliteQueryType::EMPTY)
+ {
+ newQuery->setSqliteDialect(Dialect::Sqlite2);
+ newQueries << newQuery;
+ }
+ return newQuery;
+}
+
+QList<SqliteQueryPtr> 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<SqliteQueryPtr>();
+ }
+
+ 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<SqliteCreateTable::Constraint*> tableConstrIt(createTable->constraints);
+ while (tableConstrIt.hasNext())
+ {
+ tableConstrIt.next();
+ if (tableConstrIt.value()->type == SqliteCreateTable::Constraint::PRIMARY_KEY)
+ tableConstrIt.value()->autoincrKw = false;
+ }
+
+ // Column constraints
+ QMutableListIterator<SqliteCreateTable::Column*> tableColIt(createTable->columns);
+ while (tableColIt.hasNext())
+ {
+ tableColIt.next();
+ QMutableListIterator<SqliteCreateTable::Column::Constraint*> 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<SqliteForeignKey::Condition*> 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<SqliteDelete*>(query)))
+ return false;
+
+ break;
+ }
+ case SqliteQueryType::Update:
+ {
+ if (!modifyUpdateForVersion2(dynamic_cast<SqliteUpdate*>(query)))
+ return false;
+
+ break;
+ }
+ case SqliteQueryType::Insert:
+ {
+ if (!modifyInsertForVersion2(dynamic_cast<SqliteInsert*>(query)))
+ return false;
+
+ break;
+ }
+ case SqliteQueryType::Select:
+ {
+ if (!modifySelectForVersion2(dynamic_cast<SqliteSelect*>(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<SqliteQuery>();
+ return true;
+}
+
+bool DbVersionConverter::modifyAllExprsForVersion2(SqliteStatement* stmt)
+{
+ QList<SqliteExpr*> allExprs = stmt->getAllTypedStatements<SqliteExpr>();
+ 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<SqliteOrderBy*>(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<SqliteIndexedColumn*> columns = stmt->getAllTypedStatements<SqliteIndexedColumn>();
+ return modifyAllIndexedColumnsForVersion2(columns);
+
+}
+
+bool DbVersionConverter::modifyAllIndexedColumnsForVersion2(const QList<SqliteIndexedColumn*> 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<SqliteCreateTable::Column*> tableColIt(createTable->columns);
+ while (tableColIt.hasNext())
+ {
+ tableColIt.next();
+ QMutableListIterator<SqliteCreateTable::Column::Constraint*> 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<QString,QString>(sql1, sql2);
+}
+
+void DbVersionConverter::storeErrorDiff(SqliteStatement* stmt)
+{
+ stmt->rebuildTokens();
+ diffList << QPair<QString,QString>(stmt->detokenize(), "");
+}
+
+void DbVersionConverter::reset()
+{
+ db = nullptr;
+ targetDialect = Dialect::Sqlite3;
+ diffList.clear();
+ newQueries.clear();
+ errors.clear();
+ safe_delete(resolver);
+}
+
+QList<Dialect> DbVersionConverter::getSupportedVersions() const
+{
+ QList<Dialect> 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<DbPlugin>())
+ {
+ tmpDb = plugin->getInstance("", ":memory:", QHash<QString,QVariant>());
+ if (tmpDb->initAfterCreated() && tmpDb->getDialect() == fullConversionConfig->to)
+ db = plugin->getInstance(fullConversionConfig->targetName, fullConversionConfig->targetFile, QHash<QString,QVariant>());
+
+ 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<SqliteCreateTable>();
+ 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<Db*> DbVersionConverter::getAllPossibleDbInstances() const
+{
+ QList<Db*> dbList;
+ Db* db = nullptr;
+ for (DbPlugin* plugin : PLUGINS->getLoadedPlugins<DbPlugin>())
+ {
+ db = plugin->getInstance("", ":memory:", QHash<QString,QVariant>());
+ 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<QPair<QString, QString> >& DbVersionConverter::getDiffList() const
+{
+ return diffList;
+}
+
+const QSet<QString>& DbVersionConverter::getErrors() const
+{
+ return errors;
+}
+
+const QList<SqliteQueryPtr>&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<SqliteQueryPtr> 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 <QList>
+#include <QStringList>
+#include <QPair>
+#include <QMutex>
+
+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<bool(const QList<QPair<QString,QString>>& diffs)> ConversionConfimFunction;
+ typedef std::function<bool(const QSet<QString>& 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<QPair<QString, QString> >& getDiffList() const;
+ const QSet<QString>& getErrors() const;
+ const QList<SqliteQueryPtr>& getConverted() const;
+ QStringList getConvertedSqls() const;
+ void reset();
+ QList<Dialect> 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<SqliteQueryPtr> 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<SqliteIndexedColumn*> 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<Db*> getAllPossibleDbInstances() const;
+ QString generateQueryPlaceholders(int argCount);
+ void sortConverted();
+ void setInterrupted(bool value);
+ bool isInterrupted();
+ void conversionInterrupted(Db* db, bool rollback);
+
+ template <class T>
+ QSharedPointer<T> copyQuery(SqliteQueryPtr query)
+ {
+ return QSharedPointer<T>::create(*(query.dynamicCast<T>().data()));
+ }
+
+ Db* db = nullptr;
+ Dialect targetDialect = Dialect::Sqlite3;
+ SchemaResolver* resolver = nullptr;
+ QList<QPair<QString,QString>> diffList;
+ QSet<QString> errors;
+ QList<SqliteQueryPtr> 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 <QSet>
+#include <QDebug>
+
+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<QString> 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 <QSortFilterProxyModel>
+
+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 <algorithm>
+#include <limits>
+// Code known to compile and run with Qt 4.3 through Qt 4.7.
+#include <QtCore>
+#include <time.h>
+#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> diff_match_patch::diff_main(const QString &text1,
+ const QString &text2) {
+ return diff_main(text1, text2, true);
+}
+
+QList<Diff> 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<clock_t>::max();
+ } else {
+ deadline = clock() + (clock_t)(Diff_Timeout * CLOCKS_PER_SEC);
+ }
+ return diff_main(text1, text2, checklines, deadline);
+}
+
+QList<Diff> 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<Diff> 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> diff_match_patch::diff_compute(QString text1, QString text2,
+ bool checklines, clock_t deadline) {
+ QList<Diff> 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<Diff> diffs_a = diff_main(text1_a, text2_a,
+ checklines, deadline);
+ const QList<Diff> 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> diff_match_patch::diff_lineMode(QString text1, QString text2,
+ clock_t deadline) {
+ // Scan the text on a line-by-line basis first.
+ const QList<QVariant> b = diff_linesToChars(text1, text2);
+ text1 = b[0].toString();
+ text2 = b[1].toString();
+ QStringList linearray = b[2].toStringList();
+
+ QList<Diff> 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<Diff> 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> 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<Diff> diffs;
+ diffs.append(Diff(DELETE, text1));
+ diffs.append(Diff(INSERT, text2));
+ return diffs;
+}
+
+QList<Diff> 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<Diff> diffs = diff_main(text1a, text2a, false, deadline);
+ QList<Diff> diffsb = diff_main(text1b, text2b, false, deadline);
+
+ return diffs + diffsb;
+}
+
+QList<QVariant> diff_match_patch::diff_linesToChars(const QString &text1,
+ const QString &text2) {
+ QStringList lineArray;
+ QMap<QString, int> 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<QVariant> 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<QString, int> &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<ushort>(lineHash.value(line)));
+ } else {
+ lineArray.append(line);
+ lineHash.insert(line, lineArray.size() - 1);
+ chars += QChar(static_cast<ushort>(lineArray.size() - 1));
+ }
+ }
+ return chars;
+}
+
+
+
+void diff_match_patch::diff_charsToLines(QList<Diff> &diffs,
+ const QStringList &lineArray) {
+ // Qt has no mutable foreach construct.
+ QMutableListIterator<Diff> 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<ushort>(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<Diff> &diffs) {
+ if (diffs.isEmpty()) {
+ return;
+ }
+ bool changes = false;
+ QStack<Diff> equalities; // Stack of equalities.
+ QString lastequality; // Always equal to equalities.lastElement().text
+ QMutableListIterator<Diff> 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: <del>abcxxx</del><ins>xxxdef</ins>
+ // -> <del>abc</del>xxx<ins>def</ins>
+ // e.g: <del>xxxabc</del><ins>defxxx</ins>
+ // -> <ins>def</ins>xxx<del>abc</del>
+ // 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<Diff> &diffs) {
+ QString equality1, edit, equality2;
+ QString commonString;
+ int commonOffset;
+ int score, bestScore;
+ QString bestEquality1, bestEdit, bestEquality2;
+ // Create a new iterator at the start.
+ QMutableListIterator<Diff> 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<Diff> &diffs) {
+ if (diffs.isEmpty()) {
+ return;
+ }
+ bool changes = false;
+ QStack<Diff> equalities; // Stack of equalities.
+ QString lastequality; // Always equal to equalities.lastElement().text
+ QMutableListIterator<Diff> 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:
+ * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
+ * <ins>A</ins>X<ins>C</ins><del>D</del>
+ * <ins>A</ins><del>B</del>X<ins>C</ins>
+ * <ins>A</del>X<ins>C</ins><del>D</del>
+ * <ins>A</ins><del>B</del>X<del>C</del>
+ */
+ 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<Diff> &diffs) {
+ diffs.append(Diff(EQUAL, "")); // Add a dummy entry at the end.
+ QMutableListIterator<Diff> 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: A<ins>BA</ins>C -> <ins>AB</ins>AC
+ */
+ 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<Diff> &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<Diff> &diffs) {
+ QString html;
+ QString text;
+ foreach(Diff aDiff, diffs) {
+ text = aDiff.text;
+ text.replace("&", "&amp;").replace("<", "&lt;")
+ .replace(">", "&gt;").replace("\n", "&para;<br>");
+ switch (aDiff.operation) {
+ case INSERT:
+ html += QString("<ins style=\"background:#e6ffe6;\">") + text
+ + QString("</ins>");
+ break;
+ case DELETE:
+ html += QString("<del style=\"background:#ffe6e6;\">") + text
+ + QString("</del>");
+ break;
+ case EQUAL:
+ html += QString("<span>") + text + QString("</span>");
+ break;
+ }
+ }
+ return html;
+}
+
+
+QString diff_match_patch::diff_text1(const QList<Diff> &diffs) {
+ QString text;
+ foreach(Diff aDiff, diffs) {
+ if (aDiff.operation != INSERT) {
+ text += aDiff.text;
+ }
+ }
+ return text;
+}
+
+
+QString diff_match_patch::diff_text2(const QList<Diff> &diffs) {
+ QString text;
+ foreach(Diff aDiff, diffs) {
+ if (aDiff.operation != DELETE) {
+ text += aDiff.text;
+ }
+ }
+ return text;
+}
+
+
+int diff_match_patch::diff_levenshtein(const QList<Diff> &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<Diff> &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> diff_match_patch::diff_fromDelta(const QString &text1,
+ const QString &delta) {
+ QList<Diff> 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<QChar, int> 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<float> (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<float> (Match_Distance));
+}
+
+
+QMap<QChar, int> diff_match_patch::match_alphabet(const QString &pattern) {
+ QMap<QChar, int> 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<Patch> 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<Diff> diffs = diff_main(text1, text2, true);
+ if (diffs.size() > 2) {
+ diff_cleanupSemantic(diffs);
+ diff_cleanupEfficiency(diffs);
+ }
+
+ return patch_make(text1, diffs);
+}
+
+
+QList<Patch> diff_match_patch::patch_make(const QList<Diff> &diffs) {
+ // No origin string provided, compute our own.
+ const QString text1 = diff_text1(diffs);
+ return patch_make(text1, diffs);
+}
+
+
+QList<Patch> diff_match_patch::patch_make(const QString &text1,
+ const QString &text2,
+ const QList<Diff> &diffs) {
+ // text2 is entirely unused.
+ return patch_make(text1, diffs);
+
+ Q_UNUSED(text2)
+}
+
+
+QList<Patch> diff_match_patch::patch_make(const QString &text1,
+ const QList<Diff> &diffs) {
+ // Check for null inputs.
+ if (text1.isNull()) {
+ throw "Null inputs. (patch_make)";
+ }
+
+ QList<Patch> 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<Patch> diff_match_patch::patch_deepCopy(QList<Patch> &patches) {
+ QList<Patch> 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<QString, QVector<bool> > diff_match_patch::patch_apply(
+ QList<Patch> &patches, const QString &sourceText) {
+ QString text = sourceText; // Copy to preserve original.
+ if (patches.isEmpty()) {
+ return QPair<QString,QVector<bool> >(text, QVector<bool>(0));
+ }
+
+ // Deep copy the patches so that no changes are made to originals.
+ QList<Patch> 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<bool> 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<Diff> diffs = diff_main(text1, text2, false);
+ if (text1.length() > Match_MaxBits
+ && diff_levenshtein(diffs) / static_cast<float> (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<QString, QVector<bool> >(text, results);
+}
+
+
+QString diff_match_patch::patch_addPadding(QList<Patch> &patches) {
+ short paddingLength = Patch_Margin;
+ QString nullPadding = "";
+ for (short x = 1; x <= paddingLength; x++) {
+ nullPadding += QChar((ushort)x);
+ }
+
+ // Bump all the patches forward.
+ QMutableListIterator<Patch> 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<Diff> &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<Diff> &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<Patch> &patches) {
+ short patch_size = Match_MaxBits;
+ QString precontext, postcontext;
+ Patch patch;
+ int start1, start2;
+ bool empty;
+ Operation diff_type;
+ QString diff_text;
+ QMutableListIterator<Patch> 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<Patch> &patches) {
+ QString text;
+ foreach(Patch aPatch, patches) {
+ text.append(aPatch.toString());
+ }
+ return text;
+}
+
+
+QList<Patch> diff_match_patch::patch_fromText(const QString &textline) {
+ QList<Patch> 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<Patch>();
+ }
+ 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 <time.h>
+#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 <QtCore>
+ #include <QString>
+ #include <QList>
+ #include <QMap>
+ #include <QVariant>
+ #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<QString, QVector<bool> > 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<Diff> 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> 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> 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> 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> 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> 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> 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> 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<QVariant> 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<QString, int> &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<Diff> &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<Diff> &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 c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
+ * @param diffs LinkedList of Diff objects.
+ */
+ public:
+ void diff_cleanupSemanticLossless(QList<Diff> &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<Diff> &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<Diff> &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<Diff> &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<Diff> &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<Diff> &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<Diff> &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<Diff> &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<Diff> &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> 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<QChar, int> 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> 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> patch_make(const QList<Diff> &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<Diff> &diffs).
+ */
+ public:
+ QList<Patch> patch_make(const QString &text1, const QString &text2, const QList<Diff> &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> patch_make(const QString &text1, const QList<Diff> &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> patch_deepCopy(QList<Patch> &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<QString,QVector<bool> > patch_apply(QList<Patch> &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<Patch> &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<Patch> &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<Patch> &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> 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<ExpectedToken>()
+{
+}
+
+ExpectedTokenPtr::ExpectedTokenPtr(ExpectedToken* ptr) :
+ QSharedPointer<ExpectedToken>(ptr)
+{
+}
+
+ExpectedTokenPtr::ExpectedTokenPtr(const QSharedPointer<ExpectedToken>& other) :
+ QSharedPointer<ExpectedToken>(other)
+{
+}
+
+ExpectedTokenPtr::ExpectedTokenPtr(const QWeakPointer<ExpectedToken>& other) :
+ QSharedPointer<ExpectedToken>(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 <QString>
+#include <QSharedPointer>
+
+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<ExpectedToken>
+{
+ public:
+ ExpectedTokenPtr();
+ explicit ExpectedTokenPtr(ExpectedToken* ptr);
+ explicit ExpectedTokenPtr(const QSharedPointer<ExpectedToken> & other);
+ explicit ExpectedTokenPtr(const QWeakPointer<ExpectedToken> & 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 <QMutexLocker>
+#include <QDebug>
+
+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<QueryExecutor::ResultColumnPtr> resultColumns = executor->getResultColumns();
+ QHash<ExportManager::ExportProviderFlag,QVariant> 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<ExportManager::ExportProviderFlag, QVariant> ExportWorker::getProviderDataForQueryResults()
+{
+ static const QString colLengthSql = QStringLiteral("SELECT %1 FROM (%2)");
+ static const QString colLengthTpl = QStringLiteral("max(length(%1))");
+ QHash<ExportManager::ExportProviderFlag, QVariant> 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<int> 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<ExportManager::ExportObjectPtr> 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<ExportManager::ExportObject::Type> 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<ExportManager::ExportObjectPtr>& 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<SqliteCreateIndex>());
+ break;
+ case ExportManager::ExportObject::TRIGGER:
+ res = plugin->exportTrigger(obj->database, obj->name, obj->ddl, parsedQuery.dynamicCast<SqliteCreateTrigger>());
+ break;
+ case ExportManager::ExportObject::VIEW:
+ res = plugin->exportView(obj->database, obj->name, obj->ddl, parsedQuery.dynamicCast<SqliteCreateView>());
+ 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<ExportManager::ExportProviderFlag,QVariant> 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<SqliteCreateIndexPtr> 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<SqliteCreateTriggerPtr> 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<ExportManager::ExportProviderFlag,QVariant>& providerData)
+{
+ SqliteCreateTablePtr createTable = parsedDdl.dynamicCast<SqliteCreateTable>();
+ SqliteCreateVirtualTablePtr createVirtualTable = parsedDdl.dynamicCast<SqliteCreateVirtualTable>();
+
+ 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<ExportManager::ExportObjectPtr> ExportWorker::collectDbObjects(QString* errorMessage)
+{
+ SchemaResolver resolver(db);
+ StrHash<SchemaResolver::ObjectDetails> allDetails = resolver.getAllObjectDetails();
+
+ QList<ExportManager::ExportObjectPtr> 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<ExportManager::ExportProviderFlag,QVariant>& 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<int> 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 <QObject>
+#include <QRunnable>
+#include <QMutex>
+
+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<ExportManager::ExportProviderFlag, QVariant> getProviderDataForQueryResults();
+ bool exportDatabase();
+ bool exportDatabaseObjects(const QList<ExportManager::ExportObjectPtr>& dbObjects, ExportManager::ExportObject::Type type);
+ bool exportTable();
+ bool exportTableInternal(const QString& database, const QString& table, const QString& ddl, SqliteQueryPtr parsedDdl, SqlQueryPtr results,
+ const QHash<ExportManager::ExportProviderFlag, QVariant>& providerData);
+ QList<ExportManager::ExportObjectPtr> collectDbObjects(QString* errorMessage);
+ void queryTableDataToExport(Db* db, const QString& table, SqlQueryPtr& dataPtr, QHash<ExportManager::ExportProviderFlag, QVariant>& 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 <QDebug>
+
+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<SqliteQueryPtr>& 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<QString,TokenList> groupedDbTokens = groupDbTokens(dbTokens);
+
+ if (!attachAllDbs(groupedDbTokens))
+ return false;
+
+ QHash<TokenPtr,TokenPtr> 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<QString, TokenList> DbAttacherImpl::groupDbTokens(const TokenList& dbTokens)
+{
+ // Filter out tokens of unknown databases and group results by name
+ QHash<QString,TokenList> 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<QString, TokenList>& 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<TokenPtr, TokenPtr> DbAttacherImpl::getTokenMapping(const TokenList& dbTokens)
+{
+ QHash<TokenPtr, TokenPtr> 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<TokenPtr, TokenPtr>& tokenMapping)
+{
+ int idx;
+ QHashIterator<TokenPtr,TokenPtr> 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<SqliteQueryPtr>& 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<QString,TokenList> 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 "<tt>ATTACH 'path to file'</tt>" fails for any reason. In that case
+ * detachAttached() is called and false is returned.
+ */
+ bool attachAllDbs(const QHash<QString,TokenList>& 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<TokenPtr, TokenPtr> 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<TokenPtr, TokenPtr>& tokenMapping);
+
+ QList<SqliteQueryPtr> queries;
+ Db* db = nullptr;
+ Dialect dialect;
+ BiStrHash dbNameToAttach;
+ StrHash<Db*> 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<ImportPlugin::ColumnDefinition> 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<QVariant> 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 <QObject>
+#include <QRunnable>
+#include <QMutex>
+
+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.
+<http://creativecommons.org/licenses/by/3.0/>
+
+If you can't or don't want to provide attribution, please
+purchase a royalty-free license.
+<http://p.yusukekamiyamane.com/>
+
+I'm unavailable for custom icon design work. But your
+suggestions are always welcome!
+<mailto:p@yusukekamiyamane.com>
+
+------------------------------------------------------------
+
+All logos and trademarks in some icons are property of their
+respective owners.
+
+------------------------------------------------------------
+
+- geotag
+
+ (C) Geotag Icon Project. All rights reserved.
+ <http://www.geotagicons.com/>
+
+ Geotag icon is licensed under a Creative Commons
+ Attribution-Share Alike 3.0 License or LGPL.
+ <http://creativecommons.org/licenses/by-sa/3.0/>
+ <http://opensource.org/licenses/lgpl-license.php>
+
+- language
+
+ (C) Language Icon Project. All rights reserved.
+ <http://www.languageicon.org/>
+
+ Language icon is licensed under a Creative Commons
+ Attribution-Share Alike 3.0 License.
+ <http://creativecommons.org/licenses/by-sa/3.0/>
+
+- open-share
+
+ (C) Open Share Icon Project. All rights reserved.
+ <http://www.openshareicons.com/>
+
+ Open Share icon is licensed under a Creative Commons
+ Attribution-Share Alike 3.0 License.
+ <http://creativecommons.org/licenses/by-sa/3.0/>
+
+- opml
+
+ (C) OPML Icon Project. All rights reserved.
+ <http://opmlicons.com/>
+
+ OPML icon is licensed under a Creative Commons
+ Attribution-Share Alike 2.5 License.
+ <http://creativecommons.org/licenses/by-sa/2.5/>
+
+- share
+
+ (C) Share Icon Project. All rights reserved.
+ <http://shareicons.com/>
+
+ Share icon is licensed under a GPL or LGPL or BSD or
+ Creative Commons Attribution 2.5 License.
+ <http://opensource.org/licenses/gpl-license.php>
+ <http://opensource.org/licenses/lgpl-license.php>
+ <http://opensource.org/licenses/bsd-license.php>
+ <http://creativecommons.org/licenses/by/2.5/>
+
+- xfn
+
+ (C) Wolfgang Bartelme. All rights reserved.
+ <http://www.bartelme.at/>
+
+ XFN icon is licensed under a Creative Commons
+ Attribution-Share Alike 2.5 License.
+ <http://creativecommons.org/licenses/by-sa/2.5/> \ 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. <http://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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
+<http://www.gnu.org/licenses/>.
+
+ 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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
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.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+ <signature of Ty Coon>, 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 <http://www.gnu.org/licenses/>.
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 <QTime>
+#include <QDebug>
+
+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<QString,QVariant>& 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<QString,QVariant> it(args);
+ while (it.hasNext())
+ {
+ it.next();
+ qDebug() << " SQL arg>" << it.key() << "=" << it.value();
+ }
+}
+
+void logSql(Db* db, const QString& str, const QList<QVariant>& 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 <QString>
+#include <QHash>
+#include <QList>
+#include <QVariant>
+
+API_EXPORT void logSql(Db* db, const QString& str, const QHash<QString,QVariant>& args, Db::Flags flags);
+API_EXPORT void logSql(Db* db, const QString& str, const QList<QVariant>& 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<SqliteStatement::FullObject> SqliteAlterTable::getFullObjectsInStatement()
+{
+ QList<FullObject> 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<FullObject> 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<SqliteAlterTable> 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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqliteAnalyze::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteAnalyze> 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<SqliteAttach> 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 <parser/statementtokenbuilder.h>
+
+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 <QString>
+
+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<SqliteBeginTrans> 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 <QVariant>
+
+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<SqliteColumnType> 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 <parser/statementtokenbuilder.h>
+
+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 <QString>
+
+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<SqliteCommitTrans> 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 <QString>
+
+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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqliteCopy::getFullObjectsInStatement()
+{
+ QList<FullObject> 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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteCopy> 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<SqliteIndexedColumn *> &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<SqliteIndexedColumn*>& 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<SqliteStatement::FullObject> SqliteCreateIndex::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+#include <QList>
+
+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<SqliteIndexedColumn*>& columns,
+ SqliteConflictAlgo onConflict = SqliteConflictAlgo::null);
+ SqliteCreateIndex(bool unique, bool ifNotExists, const QString& name1, const QString& name2,
+ const QString& name3, const QList<SqliteIndexedColumn*>& columns,
+ SqliteExpr* where);
+ ~SqliteCreateIndex();
+ SqliteStatement* clone();
+
+ QString getTargetTable() const;
+
+ bool uniqueKw = false;
+ bool ifNotExistsKw = false;
+ QList<SqliteIndexedColumn*> 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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteCreateIndex> 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<Column *> &columns, const QList<Constraint*>& 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<SqliteCreateTable::Column*>& columns, const QList<SqliteCreateTable::Constraint*>& 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::Constraint*> SqliteCreateTable::getConstraints(SqliteCreateTable::Constraint::Type type) const
+{
+ QList<SqliteCreateTable::Constraint*> 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<SqliteCreateTable::Column::Constraint*>(primaryKey);
+ if (columnConstr)
+ {
+ colNames << dynamic_cast<SqliteCreateTable::Column*>(columnConstr->parentStatement())->name;
+ return colNames;
+ }
+
+ SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(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::Constraint*> SqliteCreateTable::getForeignKeysByTable(const QString& foreignTable) const
+{
+ QList<Constraint*> 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::Column::Constraint*> SqliteCreateTable::getColumnForeignKeysByTable(const QString& foreignTable) const
+{
+ QList<Column::Constraint*> 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<QString, QString> SqliteCreateTable::getModifiedColumnsMap(bool lowercaseKeys, Qt::CaseSensitivity cs) const
+{
+ QHash<QString, QString> 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<SqliteStatement::FullObject> SqliteCreateTable::getFullObjectsInStatement()
+{
+ QList<FullObject> 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<SqliteIndexedColumn*>& indexedColumns, const QList<SqliteForeignKey::Condition*>& 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<SqliteIndexedColumn *> &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<SqliteIndexedColumn *> &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<SqliteIndexedColumn *> &indexedColumns, const QString& table, const QList<SqliteIndexedColumn *> &fkColumns, const QList<SqliteForeignKey::Condition *> &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<Constraint *> &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::Constraint*> SqliteCreateTable::Column::getForeignKeysByTable(const QString& foreignTable) const
+{
+ QList<Constraint*> 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 <QVariant>
+#include <QList>
+
+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<SqliteIndexedColumn*>& indexedColumns, const QList<SqliteForeignKey::Condition*>& 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<Constraint> ConstraintPtr;
+
+ Column();
+ Column(const Column& other);
+ Column(const QString& name, SqliteColumnType* type,
+ const QList<Constraint*>& constraints);
+ ~Column();
+ SqliteStatement* clone();
+
+ bool hasConstraint(Constraint::Type type) const;
+ Constraint* getConstraint(Constraint::Type type) const;
+ QList<Constraint*> getForeignKeysByTable(const QString& foreignTable) const;
+
+ QString name = QString::null;
+ SqliteColumnType* type = nullptr;
+ QList<Constraint*> 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<Column> 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<SqliteIndexedColumn*>& indexedColumns,
+ bool autoincr, SqliteConflictAlgo algo);
+ void initUnique(const QList<SqliteIndexedColumn*>& indexedColumns,
+ SqliteConflictAlgo algo);
+ void initCheck(SqliteExpr* expr, SqliteConflictAlgo algo);
+ void initCheck();
+ void initFk(const QList<SqliteIndexedColumn*>& indexedColumns, const QString& table,
+ const QList<SqliteIndexedColumn*>& fkColumns, const QList<SqliteForeignKey::Condition*>& 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<SqliteIndexedColumn*> indexedColumns;
+ bool afterComma = false;
+
+ protected:
+ TokenList rebuildTokensFromContents();
+ };
+
+ typedef QSharedPointer<Constraint> ConstraintPtr;
+
+ SqliteCreateTable();
+ SqliteCreateTable(const SqliteCreateTable& other);
+ SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2,
+ const QList<Column*>& columns, const QList<Constraint*>& constraints);
+ SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2,
+ const QList<Column*>& columns, const QList<Constraint*>& constraints,
+ const QString& withOutRowId);
+ SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2,
+ SqliteSelect* select);
+ ~SqliteCreateTable();
+ SqliteStatement* clone();
+
+ QList<Constraint*> getConstraints(Constraint::Type type) const;
+ SqliteStatement* getPrimaryKey() const;
+ QStringList getPrimaryKeyColumns() const;
+ Column* getColumn(const QString& colName);
+ QList<Constraint*> getForeignKeysByTable(const QString& foreignTable) const;
+ QList<Column::Constraint*> getColumnForeignKeysByTable(const QString& foreignTable) const;
+ QStringList getColumnNames() const;
+ QHash<QString,QString> 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<Column*> columns;
+ QList<Constraint*> constraints;
+ SqliteSelect* select = nullptr;
+ QString withOutRowId = QString::null;
+
+ protected:
+ QStringList getTablesInStatement();
+ QStringList getDatabasesInStatement();
+ TokenList getTableTokensInStatement();
+ TokenList getDatabaseTokensInStatement();
+ QList<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+
+ private:
+ void init(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2);
+
+};
+
+typedef QSharedPointer<SqliteCreateTable> 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<SqliteDelete*>(query));
+ break;
+ case SqliteQueryType::Insert:
+ newQuery = new SqliteInsert(*dynamic_cast<SqliteInsert*>(query));
+ break;
+ case SqliteQueryType::Update:
+ newQuery = new SqliteUpdate(*dynamic_cast<SqliteUpdate*>(query));
+ break;
+ case SqliteQueryType::Select:
+ newQuery = new SqliteSelect(*dynamic_cast<SqliteSelect*>(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<SqliteQuery *> &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<SqliteStatement::FullObject> SqliteCreateTrigger::getFullObjectsInStatement()
+{
+ QList<FullObject> 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<QString> &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 <QString>
+#include <QList>
+
+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<QString>& 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<SqliteQuery*>& 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<SqliteQuery*> 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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteCreateTrigger> 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<SqliteStatement::FullObject> SqliteCreateView::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteCreateView> 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 <parser/lexer.h>
+#include <parser/statementtokenbuilder.h>
+
+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<QString> &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<SqliteStatement::FullObject> SqliteCreateVirtualTable::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+#include <QList>
+
+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<QString>& args);
+
+ SqliteStatement* clone();
+
+ protected:
+ QStringList getTablesInStatement();
+ QStringList getDatabasesInStatement();
+ TokenList getTableTokensInStatement();
+ TokenList getDatabaseTokensInStatement();
+ QList<FullObject> 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<QString> args;
+};
+
+typedef QSharedPointer<SqliteCreateVirtualTable> 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 <QString>
+
+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<SqliteStatement::FullObject> SqliteDelete::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<FullObject> 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<SqliteDelete> 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<SqliteDetach> 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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqliteDropIndex::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteDropIndex> 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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqliteDropTable::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteDropTable> 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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqliteDropTrigger::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteDropTrigger> 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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqliteDropView::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteDropView> 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<SqliteEmptyQuery> 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 <QDebug>
+
+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<SqliteExpr*>& 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<SqliteExpr*>& 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<SqliteExpr*>& 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<SqliteStatement::FullObject> SqliteExpr::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+#include <QVariant>
+#include <QList>
+
+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<SqliteExpr*>& 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<SqliteExpr*>& 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<SqliteExpr*>& 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<SqliteExpr*> 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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+ void evaluatePostParsing();
+
+ private:
+ TokenList rebuildId();
+ TokenList rebuildLike();
+ TokenList rebuildNotNull();
+ TokenList rebuildIs();
+ TokenList rebuildBetween();
+ TokenList rebuildIn();
+ TokenList rebuildCase();
+};
+
+typedef QSharedPointer<SqliteExpr> 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 <QDebug>
+
+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<SqliteStatement::FullObject> SqliteForeignKey::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QList>
+#include <QString>
+
+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<SqliteIndexedColumn*> indexedColumns;
+ QList<Condition*> 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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteForeignKey> 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 <QString>
+
+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<SqliteIndexedColumn> 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<QString> &columns,
+ const QList<SqliteExpr *> &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<QString> &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<QString> &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<SqliteStatement::FullObject> SqliteInsert::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+#include <QList>
+
+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<QString>& columns,
+ const QList<SqliteExpr*>& row, SqliteWith* with);
+ SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString& name1,
+ const QString& name2, const QList<QString>& columns, SqliteSelect* select, SqliteWith* with);
+ SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString& name1,
+ const QString& name2, const QList<QString>& columns, SqliteWith* with);
+ ~SqliteInsert();
+
+ SqliteStatement* clone();
+
+ protected:
+ QStringList getColumnsInStatement();
+ QStringList getTablesInStatement();
+ QStringList getDatabasesInStatement();
+ TokenList getColumnTokensInStatement();
+ TokenList getTableTokensInStatement();
+ TokenList getDatabaseTokensInStatement();
+ QList<FullObject> 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<SqliteExpr*> values;
+ SqliteSelect* select = nullptr;
+ SqliteWith* with = nullptr;
+};
+
+typedef QSharedPointer<SqliteInsert> 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<SqliteLimit> 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<SqliteOrderBy> 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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqlitePragma::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+#include <QVariant>
+
+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<FullObject> 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<SqlitePragma> 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<SqliteQuery> 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 <QString>
+
+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 <QString>
+
+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<SqliteRaise> 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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqliteReindex::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<SqliteStatement::FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteReindex> 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 <parser/statementtokenbuilder.h>
+
+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 <QString>
+
+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<SqliteRelease> 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 <parser/statementtokenbuilder.h>
+
+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 <QString>
+
+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<SqliteRollback> 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 <parser/statementtokenbuilder.h>
+
+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 <QString>
+
+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<SqliteSavepoint> 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 <QSet>
+
+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<QList<SqliteExpr*>>& values)
+{
+ return append(nullptr, CompoundOperator::null, values);
+}
+
+SqliteSelect* SqliteSelect::append(SqliteSelect* select, SqliteSelect::CompoundOperator op, const QList<QList<SqliteExpr*>>& values)
+{
+ if (!select)
+ select = new SqliteSelect();
+
+ bool first = true;
+
+ Core::ResultColumn* resCol = nullptr;
+ QList<Core::ResultColumn*> resColList;
+ foreach (const QList<SqliteExpr*>& 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<ResultColumn *> &resCols, SqliteSelect::Core::JoinSource *src, SqliteExpr *where, const QList<SqliteExpr *> &groupBy, SqliteExpr *having, const QList<SqliteOrderBy*>& 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<SqliteStatement::FullObject> SqliteSelect::Core::ResultColumn::getFullObjectsInStatement()
+{
+ QList<FullObject> 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<SqliteStatement::FullObject> SqliteSelect::Core::SingleSource::getFullObjectsInStatement()
+{
+ QList<FullObject> 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<QString> &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<SqliteSelect::Core::JoinSourceOther *> &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 <QList>
+
+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<FullObject> 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<FullObject> 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<QString>& strList);
+
+ SqliteStatement* clone();
+
+ SqliteExpr* expr = nullptr;
+ QList<QString> 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<JoinSourceOther*>& list);
+
+ SqliteStatement* clone();
+
+ SingleSource* singleSource = nullptr;
+ QList<JoinSourceOther*> otherSources;
+
+ protected:
+ TokenList rebuildTokensFromContents();
+ };
+
+ Core();
+ Core(const Core& other);
+ Core(int distinct, const QList<ResultColumn*>& resCols, JoinSource* src, SqliteExpr* where,
+ const QList<SqliteExpr*>& groupBy, SqliteExpr* having, const QList<SqliteOrderBy*>& orderBy,
+ SqliteLimit* limit);
+
+ SqliteStatement* clone();
+
+ CompoundOperator compoundOp = CompoundOperator::null;
+ QList<ResultColumn*> resultColumns;
+ JoinSource* from = nullptr;
+ bool distinctKw = false;
+ bool allKw = false;
+ SqliteExpr* where = nullptr;
+ SqliteExpr* having = nullptr;
+ QList<SqliteExpr*> groupBy;
+ QList<SqliteOrderBy*> 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<QList<SqliteExpr*>>& values);
+ static SqliteSelect* append(SqliteSelect* select, SqliteSelect::CompoundOperator op, const QList<QList<SqliteExpr*>>& values);
+
+ SqliteStatement* clone();
+ QString compoundOperator(CompoundOperator op);
+ CompoundOperator compoundOperator(const QString& op);
+ void reset();
+ void setWith(SqliteWith* with);
+
+ QList<Core*> coreSelects;
+ SqliteWith* with = nullptr;
+
+ protected:
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteSelect> 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 <QString>
+
+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 <QDebug>
+
+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::FullObject> SqliteStatement::getContextFullObjects(bool checkParent, bool checkChilds)
+{
+ QList<FullObject> fullObjects = getContextFullObjects(this, checkParent, checkChilds);
+
+ FullObject fullObj;
+ QMutableListIterator<FullObject> 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::FullObject> SqliteStatement::getContextFullObjects(SqliteStatement* caller, bool checkParent, bool checkChilds)
+{
+ QList<SqliteStatement::FullObject> 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::FullObject> SqliteStatement::getFullObjectsInStatement()
+{
+ return QList<SqliteStatement::FullObject>();
+}
+
+TokenList SqliteStatement::rebuildTokensFromContents()
+{
+ qCritical() << "called rebuildTokensFromContents() for SqliteStatement that has no implementation for it.";
+ return TokenList();
+}
+
+void SqliteStatement::evaluatePostParsing()
+{
+}
+
+QList<SqliteStatement *> SqliteStatement::getContextStatements(SqliteStatement *caller, bool checkParent, bool checkChilds)
+{
+ QList<SqliteStatement *> 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<SqliteStatement*>(parent());
+}
+
+QList<SqliteStatement *> SqliteStatement::childStatements()
+{
+ QList<SqliteStatement*> results;
+ foreach (QObject* obj, children())
+ results += dynamic_cast<SqliteStatement*>(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<SqliteStatement*>(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 <QList>
+#include <QHash>
+#include <QObject>
+#include <QPair>
+#include <QStringList>
+#include <QSharedPointer>
+
+// TODO start using attach() in most cases where setParent() is used
+
+class SqliteStatement;
+typedef QSharedPointer<SqliteStatement> 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:
+ * <ul>
+ * <li>Parse SQL query string,</li>
+ * <li>Modify values in parsed statements,</li>
+ * <li>Re-generate tokens in all modified statements,</li>
+ * <li>Detokenize tokens from statements.</li>
+ * </ul>
+ *
+ * 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<SqliteSelect>();
+ * 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: <tt>SELECT column, 'test value' FROM test WHERE value = 5</tt>.
+ *
+ * @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<SqliteSelect>();
+ * 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<SqliteExpr>();
+ * return expr;
+ * @endcode
+ *
+ * After the above <tt>parser</tt> 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 <tt>expr</tt> 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<SqliteStatement*> 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<FullObject> 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 <class T>
+ void attach(QList<T*>& listMemberForChild, T* childStatementToAttach)
+ {
+ listMemberForChild << childStatementToAttach;
+ childStatementToAttach->setParent(this);
+ }
+
+ template <class X>
+ QSharedPointer<X> detach() {return detach().dynamicCast<X>();}
+
+ template <class T>
+ QList<T*> getAllTypedStatements()
+ {
+ QList<T*> results;
+
+ T* casted = dynamic_cast<T*>(this);
+ if (casted)
+ results << casted;
+
+ foreach (SqliteStatement* stmt, getContextStatements(this, false, true))
+ results += stmt->getAllTypedStatements<T>();
+
+ 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<QString,TokenList> 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<FullObject> 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<FullObject> 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<SqliteStatement*> 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<SqliteTableRelatedDdl> 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 <QDebug>
+
+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<QPair<QString,SqliteExpr*> > 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<SqliteStatement::FullObject> SqliteUpdate::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QStringList>
+#include <QMap>
+
+class SqliteExpr;
+class SqliteWith;
+
+class API_EXPORT SqliteUpdate : public SqliteQuery
+{
+ public:
+ typedef QPair<QString,SqliteExpr*> ColumnAndValue;
+
+ SqliteUpdate();
+ SqliteUpdate(const SqliteUpdate& other);
+ ~SqliteUpdate();
+ SqliteUpdate(SqliteConflictAlgo onConflict, const QString& name1, const QString& name2,
+ bool notIndexedKw, const QString& indexedBy, const QList<QPair<QString,SqliteExpr*> > 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<ColumnAndValue> keyValueMap;
+ SqliteExpr* where = nullptr;
+ SqliteWith* with = nullptr;
+
+ protected:
+ QStringList getColumnsInStatement();
+ QStringList getTablesInStatement();
+ QStringList getDatabasesInStatement();
+ TokenList getColumnTokensInStatement();
+ TokenList getTableTokensInStatement();
+ TokenList getDatabaseTokensInStatement();
+ QList<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteUpdate> 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 <parser/statementtokenbuilder.h>
+
+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<SqliteStatement::FullObject> SqliteVacuum::getFullObjectsInStatement()
+{
+ QList<FullObject> 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 <QString>
+
+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<FullObject> getFullObjectsInStatement();
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteVacuum> 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<SqliteIndexedColumn*>& 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<SqliteIndexedColumn*>& 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<SqliteIndexedColumn*>& 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<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select);
+
+ SqliteStatement* clone();
+
+ QString table;
+ QList<SqliteIndexedColumn*> indexedColumns;
+ SqliteSelect* select = nullptr;
+
+ protected:
+ TokenList rebuildTokensFromContents();
+ };
+
+ SqliteWith();
+ SqliteWith(const SqliteWith& other);
+ static SqliteWith* append(const QString& tableName, const QList<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select);
+ static SqliteWith* append(SqliteWith* with, const QString& tableName, const QList<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select);
+
+ SqliteStatement* clone();
+
+ QList<CommonTableExpression*> cteList;
+ bool recursive = false;
+
+ protected:
+ TokenList rebuildTokensFromContents();
+};
+
+typedef QSharedPointer<SqliteWith> 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 <QDebug>
+#include <QList>
+
+QHash<QString,int> keywords2;
+QHash<QString,int> keywords3;
+QSet<QString> 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<QString,int>& getKeywords2()
+{
+ return keywords2;
+}
+
+const QHash<QString,int>& 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 <QString>
+#include <QStringList>
+#include <QHash>
+
+/** @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: <tt>ROWID</tt>, <tt>_ROWID_</tt> and <tt>OID</tt>.
+ * 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<QString,int>& 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<QString,int>& getKeywords3();
+
+/**
+ * @brief Provides list of keywords representing types of SQL joins.
+ * @return Join type keywords.
+ *
+ * Join type keywords are: <tt>NATURAL</tt>, <tt>LEFT</tt>, <tt>RIGHT</tt>, <tt>OUTER</tt>, <tt>INNER</tt>, <tt>CROSS</tt>.
+ *
+ * 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: <tt>SIMPLE</tt>, <tt>FULL</tt> and <tt>PARTIAL</tt>.
+ *
+ * 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: <tt>ROLLBACK</tt>, <tt>ABORT</tt>, <tt>FAIL</tt>, <tt>IGNORE</tt>, <tt>REPLACE</tt>.
+ *
+ * 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 <stdio.h>
+%%
+/* 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<Token*>* 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 <stdio.h>
+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<Token*>();
+ *(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<Token*>();
+ *(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:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** 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:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from ParseAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+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( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0
+ && parserContext->doFallbacks ){
+#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<YY_ACTTAB_COUNT &&
+#endif
+ yy_lookahead[j]==YYWILDCARD
+ ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sWILDCARD %s => %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 && i<YY_ACTTAB_COUNT );
+ assert( yy_lookahead[i]==iLookAhead );
+#endif
+ return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
+ ParseARG_FETCH;
+ yypParser->yyidx--;
+#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<Token*>();
+#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 <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** 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<Token*> allTokens;
+ QList<Token*> 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<Token*> 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<Token*>* 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:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** 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<Token*>();
+ }
+ 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( yyact<YYNSTATE ){
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ 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 <QString>
+#include <QMultiHash>
+#include <QDebug>
+
+QHash<Token::Type,QSet<TokenPtr> > Lexer::everyTokenType2;
+QHash<Token::Type,QSet<TokenPtr> > Lexer::everyTokenType3;
+QHash<Token*,TokenPtr> 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<TokenPtr> 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<TokenPtr> Lexer::getEveryTokenType(QSet<Token::Type> types)
+{
+ // Process set of types
+ QSet<TokenPtr> results;
+ QHashIterator<Token::Type,QSet<TokenPtr> > i(
+ dialect == Dialect::Sqlite2 ? everyTokenType2 : everyTokenType3
+ );
+ while (i.hasNext())
+ {
+ i.next();
+ if (types.contains(i.key()))
+ {
+ QSet<TokenPtr> 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<QString,int> 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<QString,int> 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 <QList>
+#include <QString>
+#include <QSet>
+
+/**
+ * @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<TokenPtr> 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<TokenPtr> getEveryTokenType(QSet<Token::Type> 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<Token::Type,QSet<TokenPtr> > 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<Token::Type,QSet<TokenPtr> > everyTokenType3;
+
+ /**
+ * @brief Map of every token type pointer to its QSharedPointer from internal tables.
+ *
+ * This is used by getEveryTokenTypePtr().
+ */
+ static QHash<Token*,TokenPtr> 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 <QByteArray>
+#include <QChar>
+#include <QDebug>
+
+//
+// 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<TolerantToken>())
+ {
+ 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<TolerantToken>()->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<TolerantToken>()->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<TolerantToken>()->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<TolerantToken>()->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<TolerantToken>()->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 <QString>
+
+/** @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 <QStringList>
+#include <QDebug>
+
+// 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<char*>("[LEMON2]: ");
+ else
+ label = const_cast<char*>("[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<SqliteSelect>("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<TokenPtr> 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<ParserError *> &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<SqliteQueryPtr>& 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<SqliteQueryPtr> queries = parser.getQueries();
+ * qDebug() << "number of queries parsed:" << queries.size();
+ * foreach (SqliteQueryPtr query, queries)
+ * {
+ * // do stuff with parsed queries
+ * // ...
+ * if (query.dynamicCast<SqliteSelect>())
+ * {
+ * qDebug() << "it's a select!";
+ * }
+ * }
+ * }
+ * else
+ * {
+ * qDebug() << "Error while parsing:" << parser.getErrorString();
+ * }
+ * @endcode
+ *
+ * There's also a convenient parse<T>() 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 <tt>database.table.</tt> (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 <tt>"SELECT * FROM WHERE"</tt>, 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<SelectPtr>(queryString);
+ * if (!select)
+ * {
+ * qCritical() << "Could not parse" << queryString << "to a SELECT statement, details:" << parser.getErrorString();
+ * return;
+ * }
+ * // do stuff with the 'select' object
+ * // ...
+ * @endcode
+ */
+ template <class T>
+ QSharedPointer<T> parse(const QString& query)
+ {
+ if (!parse(query) || getQueries().size() == 0)
+ return QSharedPointer<T>();
+
+ return getQueries().first().dynamicCast<T>();
+ }
+
+ /**
+ * @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 <b>types</b>, 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<SqliteQueryPtr>& 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<ParserError*>& 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<T>(), 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 <QString>
+#include <QList>
+
+/** @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 <tt>EXPLAIN</tt> and <tt>QUERY PLAN</tt> grammar rules.
+ */
+struct ParserStubExplain
+{
+ ParserStubExplain(bool explain, bool queryPlan);
+
+ bool explain;
+ bool queryPlan;
+};
+
+/**
+ * @brief Stores "<tt>OR</tt> conflict" grammar rules.
+ */
+struct ParserStubInsertOrReplace
+{
+ explicit ParserStubInsertOrReplace(bool replace);
+ ParserStubInsertOrReplace(bool replace, SqliteConflictAlgo orConflict);
+
+ bool replace;
+ SqliteConflictAlgo orConflict;
+};
+
+/**
+ * @brief Stores grammar rules for <tt>BEGIN/END/COMMIT/ROLLBACK</tt> 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<SqliteCreateTable::Column*> ParserCreateTableColumnList;
+typedef QList<SqliteCreateTable::Constraint*> ParserCreateTableConstraintList;
+typedef QList<SqliteCreateTable::Column::Constraint*> ParserCreateTableColumnConstraintList;
+typedef QList<SqliteForeignKey::Condition*> ParserFkConditionList;
+typedef QList<SqliteExpr*> ParserExprList;
+typedef QList<SqliteSelect::Core::ResultColumn*> ParserResultColumnList;
+typedef QList<SqliteSelect::Core::JoinSourceOther*> ParserOtherSourceList;
+typedef QList<QString> ParserStringList;
+typedef QList<SqliteOrderBy*> ParserOrderByList;
+typedef QList<SqliteQuery*> ParserQueryList;
+typedef QPair<QString,SqliteExpr*> ParserSetValue;
+typedef QList<ParserSetValue> ParserSetValueList;
+typedef QList<SqliteIndexedColumn*> ParserIndexedColumnList;
+typedef QList<ParserExprList> ParserExprNestedList;
+
+/**
+ * @brief Stores parameters for defferable foreign keys.
+ */
+struct ParserDeferSubClause
+{
+ ParserDeferSubClause(SqliteDeferrable deferrable, SqliteInitially initially);
+
+ SqliteInitially initially;
+ SqliteDeferrable deferrable;
+};
+
+/**
+ * @brief Stores "<tt>AS</tt> aliasName" grammar rule.
+ */
+struct ParserStubAlias
+{
+ ParserStubAlias(const QString& name, bool asKw);
+
+ QString name = QString::null;
+ bool asKw = false;
+};
+
+/**
+ * @brief Stores <tt>NOT INDEXED/INDEXED BY</tt> 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 <QDebug>
+
+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<Token*>& 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<SqliteQueryPtr>& ParserContext::getQueries()
+{
+ return parsedQueries;
+}
+
+const QList<ParserError *> &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 <QHash>
+#include <QList>
+#include <QSet>
+
+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 <tt>"CREATE TABLE ... (...) WITHOUT ROWID"</tt>. The SQLite grammar
+ * rule says, that the <tt>"ROWID"</tt> at the end is not necessarily the <tt>ROWID</tt> 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<Token*>& 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<SqliteQueryPtr>& getQueries();
+
+ /**
+ * @brief Provides access to all errors occurred so far.
+ * @return List of errors.
+ */
+ const QList<ParserError*>& 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<SqliteQueryPtr> parsedQueries;
+
+ /**
+ * @brief Tokens managed by this context.
+ */
+ TokenList managedTokens;
+
+ /**
+ * @brief Mapping from token pointer to it's shared pointer instance.
+ */
+ QHash<Token*, TokenPtr> 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<ParserError*> 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 <QString>
+
+/**
+ * @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: <tt>"position: message"</tt>.
+ */
+ 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 <stdio.h>
+
+#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 <QObject>
+#include <QDebug>
+
+#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<Token*>* 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 <stdio.h>
+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<Token*>();
+ *(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<Token*>();
+ *(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:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** 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:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from sqlite2_parseAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+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( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0
+ && parserContext->doFallbacks ){
+#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<YY_ACTTAB_COUNT &&
+#endif
+ yy_lookahead[j]==YYWILDCARD
+ ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sWILDCARD %s => %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 && i<YY_ACTTAB_COUNT );
+ assert( yy_lookahead[i]==iLookAhead );
+#endif
+ return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
+ sqlite2_parseARG_FETCH;
+ yypParser->yyidx--;
+#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<Token*>();
+#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 <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** 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<SqliteQuery *> 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<Token*> allTokens;
+ QList<Token*> 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<Token*> 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<Token*>* 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:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** 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<Token*>();
+ }
+ 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( yyact<YYNSTATE ){
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ 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 <QObject>
+#include <QDebug>
+
+#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 <id>" 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<SqliteQuery *> 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 <stdio.h>
+
+#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 <QObject>
+#include <QDebug>
+
+#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<Token*>* 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 <stdio.h>
+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<Token*>();
+ *(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<Token*>();
+ *(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:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** 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:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from sqlite3_parseAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+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( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0
+ && parserContext->doFallbacks ){
+#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<YY_ACTTAB_COUNT &&
+#endif
+ yy_lookahead[j]==YYWILDCARD
+ ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sWILDCARD %s => %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 && i<YY_ACTTAB_COUNT );
+ assert( yy_lookahead[i]==iLookAhead );
+#endif
+ return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
+ sqlite3_parseARG_FETCH;
+ yypParser->yyidx--;
+#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<Token*>();
+#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 <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** 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<SqliteQuery *> 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<Token*> allTokens;
+ QList<Token*> 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<Token*> 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<Token*>* 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:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** 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<Token*>();
+ }
+ 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( yyact<YYNSTATE ){
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ 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 <QObject>
+#include <QDebug>
+
+#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 <id>" 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<SqliteQuery *> 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 <QVariant>
+
+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<QString>& 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<QString>& 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<QString>& 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<QString>& 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 (<tt>"("</tt>).
+ * @return Reference to the builder for the further building.
+ */
+ StatementTokenBuilder& withParLeft();
+
+ /**
+ * @brief Adds right parenthesis token (<tt>")"</tt>).
+ * @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 <tt>"ASC"/"DESC"</tt> 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 <tt>X'...'</tt>
+ * 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 <class T>
+ StatementTokenBuilder& withStatementList(QList<T*> 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 <stdio.h>
+#include <QStringList>
+
+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<qint64>(token.data());
+}
+
+TokenList::TokenList()
+ : QList<TokenPtr>()
+{
+}
+
+TokenList::TokenList(const QList<TokenPtr>& other)
+ : QList<TokenPtr>(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<TokenPtr>::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<TokenPtr>::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<TokenPtr>::insert(i++, token);
+}
+
+void TokenList::insert(int i, TokenPtr token)
+{
+ QList<TokenPtr>::insert(i, token);
+}
+
+TokenList &TokenList::operator =(const QList<TokenPtr> &other)
+{
+ QList<TokenPtr>::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<TokenPtr>::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<TokenPtr>::mid(pos, length);
+ return newList;
+}
+
+TokenPtr TokenList::findFirst(Token::Type type, int *idx) const
+{
+ int i = -1;
+ TokenPtr token;
+ QListIterator<TokenPtr> 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<TokenPtr> 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<TokenPtr> 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<TokenPtr> 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<TokenPtr> 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<TokenPtr> 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 <QString>
+#include <QList>
+#include <QSharedPointer>
+
+/** @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<Token> 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 <tt>CTX_</tt>.
+ */
+ 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 (<tt>:param</tt>, <tt>\@param</tt>, or <tt>?</tt>). */
+ OPERATOR = 0x0008, /**< An operator (like <tt>";"</tt>, <tt>"+"</tt>, <tt>","</tt>, etc). */
+ PAR_LEFT = 0x0009, /**< A left parenthesis (<tt>"("</tt>). */
+ PAR_RIGHT = 0x0010, /**< A right parenthesis (<tt>")"</tt>). */
+ SPACE = 0x0011, /**< White space(s), including new line characters and tabs. */
+ BLOB = 0x0012, /**< Literal BLOB value (<tt>X'...'</tt> or <tt>x'...'</tt>). */
+ 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 <tt>NEW</tt> keyword is valid at this token position. */
+ CTX_OLD_KW = 0x0036, /**< The <tt>OLD</tt> 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: <tt>{type value start end}</tt>
+ */
+ 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<TolerantToken> 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<TokenPtr>
+{
+ 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<TokenPtr>& other);
+
+ /**
+ * @brief Serializes contents of the list into readable form.
+ * @return Contents in format: <tt>{type1 value1 start1 end1} {type2 value2 start2 end2} ...</tt>.
+ *
+ * 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 <tt>=</tt> operator.
+ */
+ TokenList& operator=(const QList<TokenPtr>& 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<Plugin*> 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 <QPluginLoader>
+#include <QList>
+#include <QHash>
+#include <QDebug>
+
+class Plugin;
+
+class API_EXPORT AbstractPluginLoader
+{
+ public:
+ virtual ~AbstractPluginLoader();
+
+ virtual bool add(QPluginLoader* loader, Plugin* plugin);
+ virtual bool test(Plugin* plugin) = 0;
+ QList<Plugin*> getPlugins() const;
+
+ private:
+ QHash<QPluginLoader*,Plugin*> plugins;
+};
+
+template <class T>
+class PluginLoader : public AbstractPluginLoader
+{
+ public:
+ bool test(Plugin* plugin)
+ {
+ return (dynamic_cast<T*>(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 <QMetaClassInfo>
+
+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:
+ * <ul>
+ * <li>::SQLITESTUDIO_PLUGIN_TITLE</li>
+ * <li>::SQLITESTUDIO_PLUGIN_DESC</li>
+ * <li>::SQLITESTUDIO_PLUGIN_UI</li>
+ * <li>::SQLITESTUDIO_PLUGIN_VERSION</li>
+ * <li>::SQLITESTUDIO_PLUGIN_AUTHOR</li>
+ * </ul>
+ *
+ * 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 <QVariant>
+
+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<QString,QVariant> &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<DbPluginOption> 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 <QFileInfo>
+
+Db* DbPluginSqlite3::getInstance(const QString& name, const QString& path, const QHash<QString, QVariant>& 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<DbPluginOption> DbPluginSqlite3::getOptionsList() const
+{
+ return QList<DbPluginOption>();
+}
+
+QString DbPluginSqlite3::generateDbName(const QVariant& baseValue)
+{
+ QFileInfo file(baseValue.toString());
+ return file.baseName();
+}
+
+bool DbPluginSqlite3::checkIfDbServedByPlugin(Db* db) const
+{
+ return (db && dynamic_cast<DbSqlite3*>(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<QString, QVariant>& options, QString* errorMessage);
+ QString getLabel() const;
+ QList<DbPluginOption> 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<QueryExecutor::ResultColumnPtr>& columns,
+ const QHash<ExportManager::ExportProviderFlag,QVariant> 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<ExportManager::ExportProviderFlag,QVariant> 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<ExportManager::ExportProviderFlag,QVariant> 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 <QTextCodec>
+
+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 <QMetaClassInfo>
+
+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 <QObject>
+#include <QtPlugin>
+#include <QHash>
+#include <QVariant>
+
+/** @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<QString,QVariant> 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<QString,QString> 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 <tt>INTEGER</tt> type and the second of <tt>VARCHAR(0, 5)</tt> type.
+ * You would write it like this:
+ * @code
+ * QList<ColumnDefinition> list;
+ * list << ColumnDefinition("column1", "INTEGER");
+ * list << ColumnDefinition("column2", "VARCHAR (0, 5)");
+ * return list;
+ * @endcode
+ */
+ virtual QList<ColumnDefinition> 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<QVariant> 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 <QString>
+#include <QHash>
+#include <QtPlugin>
+
+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:
+ * <ul>
+ * <li><tt>{current_executable_dir}/plugins</tt> - a "plugins" subdirectory of the directory where application binary is placed,</li>
+ * <li><tt>{configuration_dir}/plugins</tt> - a "plugins" subdirectory of configuration directory detected and defined in Config,</li>
+ * <li><tt>{env_var:SQLITESTUDIO_PLUGINS}</tt> - environment variable with name "SQLITESTUDIO_PLUGINS",</li>
+ * <li><tt>{compile_time:PLUGINS_DIR}</tt> - compile time defined parameter's value of parameter with the name "PLUGINS_DIR".</li>
+ * </ul>
+ */
+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 <QCoreApplication>
+#include <QDir>
+
+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 <QStringList>
+#include <QLibrary>
+
+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 <QDebug>
+
+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<Plugin*> PluginType::getLoadedPlugins() const
+{
+ PluginType* type = const_cast<PluginType*>(this);
+ return PLUGINS->getLoadedPlugins(type);
+}
+
+QStringList PluginType::getAllPluginNames() const
+{
+ PluginType* type = const_cast<PluginType*>(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 <QList>
+#include <QString>
+#include <QObject>
+
+class Plugin;
+
+template <class T>
+class DefinedPluginType;
+
+class API_EXPORT PluginType
+{
+ public:
+ virtual ~PluginType();
+
+ QString getName() const;
+ QString getTitle() const;
+ QString getConfigUiForm() const;
+ QList<Plugin*> getLoadedPlugins() const;
+ QStringList getAllPluginNames() const;
+
+ virtual bool test(Plugin* plugin) = 0;
+
+ template <class T>
+ bool isForPluginType()
+ {
+ return dynamic_cast<const DefinedPluginType<T>*>(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 T>
+class DefinedPluginType : public PluginType
+{
+ friend class PluginManager;
+
+ public:
+ bool test(Plugin* plugin)
+ {
+ return (dynamic_cast<T*>(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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateConstantConfig</class>
+ <widget class="QWidget" name="PopulateConstantConfig">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>77</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="valueGroup">
+ <property name="title">
+ <string>Constant value:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLineEdit" name="valueEdit">
+ <property name="cfg" stdset="0">
+ <string>PopulateConstant.Value</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
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 <QFileInfo>
+#include <QFile>
+#include <QTextStream>
+
+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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateDictionaryConfig</class>
+ <widget class="QWidget" name="PopulateDictionaryConfig">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>307</width>
+ <height>255</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="fileGroup">
+ <property name="title">
+ <string>Dictionary file</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="FileEdit" name="fileEdit" native="true">
+ <property name="cfg" stdset="0">
+ <string>PopulateDictionary.File</string>
+ </property>
+ <property name="dialogTitle" stdset="0">
+ <string>Pick dictionary file</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="separatorGroup">
+ <property name="title">
+ <string>Word separator</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="ConfigRadioButton" name="whitespaceRadio">
+ <property name="cfg" stdset="0">
+ <string>PopulateDictionary.Lines</string>
+ </property>
+ <property name="text">
+ <string>Whitespace</string>
+ </property>
+ <property name="assignedValue" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ConfigRadioButton" name="libeBreakRadio">
+ <property name="cfg" stdset="0">
+ <string>PopulateDictionary.Lines</string>
+ </property>
+ <property name="text">
+ <string>Line break</string>
+ </property>
+ <property name="assignedValue" stdset="0">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="methodGroup">
+ <property name="title">
+ <string>Method of using words</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="ConfigRadioButton" name="orderedRadio">
+ <property name="cfg" stdset="0">
+ <string>PopulateDictionary.Random</string>
+ </property>
+ <property name="text">
+ <string>Ordered</string>
+ </property>
+ <property name="assignedValue" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ConfigRadioButton" name="randomlyRadio">
+ <property name="cfg" stdset="0">
+ <string>PopulateDictionary.Random</string>
+ </property>
+ <property name="text">
+ <string>Randomly</string>
+ </property>
+ <property name="assignedValue" stdset="0">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ConfigRadioButton</class>
+ <extends>QRadioButton</extends>
+ <header>common/configradiobutton.h</header>
+ </customwidget>
+ <customwidget>
+ <class>FileEdit</class>
+ <extends>QWidget</extends>
+ <header>common/fileedit.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
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 <QDateTime>
+
+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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateRandomConfig</class>
+ <widget class="QWidget" name="PopulateRandomConfig">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>144</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="0">
+ <widget class="QGroupBox" name="prefixGroup">
+ <property name="title">
+ <string>Constant prefix</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QLineEdit" name="prefixEdit">
+ <property name="cfg" stdset="0">
+ <string>PopulateRandom.Prefix</string>
+ </property>
+ <property name="placeholderText">
+ <string>No prefix</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="minGroup">
+ <property name="title">
+ <string>Minimum value</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QSpinBox" name="minSpin">
+ <property name="cfg" stdset="0">
+ <string>PopulateRandom.MinValue</string>
+ </property>
+ <property name="minimum">
+ <number>-999999999</number>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QGroupBox" name="maxGroup">
+ <property name="title">
+ <string>Maximum value</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QSpinBox" name="maxSpin">
+ <property name="cfg" stdset="0">
+ <string>PopulateRandom.MaxValue</string>
+ </property>
+ <property name="minimum">
+ <number>-999999999</number>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="value">
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QGroupBox" name="suffixGroup">
+ <property name="title">
+ <string>Constant suffix</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="suffixEdit">
+ <property name="cfg" stdset="0">
+ <string>PopulateRandom.Suffix</string>
+ </property>
+ <property name="placeholderText">
+ <string>No suffix</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateRandomTextConfig</class>
+ <widget class="QWidget" name="PopulateRandomTextConfig">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>367</width>
+ <height>291</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0" colspan="2">
+ <widget class="ConfigRadioButton" name="commonSetRadio">
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.UseCustomSets</string>
+ </property>
+ <property name="text">
+ <string>Use characters from common sets:</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="assignedValue" stdset="0">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="minLengthGroup">
+ <property name="title">
+ <string>Minimum length</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QSpinBox" name="minLengthSpin">
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.MinLength</string>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QFrame" name="commonSetFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="alphaCheck">
+ <property name="toolTip">
+ <string>Letters from a to z.</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.IncludeAlpha</string>
+ </property>
+ <property name="text">
+ <string>Alpha</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="numericCheck">
+ <property name="toolTip">
+ <string>Numbers from 0 to 9.</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.IncludeNumeric</string>
+ </property>
+ <property name="text">
+ <string>Numeric</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="whitespaceCheck">
+ <property name="toolTip">
+ <string>A whitespace, a tab and a new line character.</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.IncludeWhitespace</string>
+ </property>
+ <property name="text">
+ <string>Whitespace</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="binaryCheck">
+ <property name="toolTip">
+ <string>Includes all above and all others.</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.IncludeBinary</string>
+ </property>
+ <property name="text">
+ <string>Binary</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="ConfigRadioButton" name="customSetRadio">
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.UseCustomSets</string>
+ </property>
+ <property name="text">
+ <string>Use characters from my custom set:</string>
+ </property>
+ <property name="assignedValue" stdset="0">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QGroupBox" name="maxLengthGroup">
+ <property name="title">
+ <string>Maximum length</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QSpinBox" name="maxLengthSpin">
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.MaxLength</string>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="2">
+ <widget class="QFrame" name="customSetFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLineEdit" name="customSetEdit">
+ <property name="toolTip">
+ <string>If you type some character multiple times, it's more likely to be used.</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>PopulateRandomText.CustomCharacters</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ConfigRadioButton</class>
+ <extends>QRadioButton</extends>
+ <header>common/configradiobutton.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
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<ScriptingPlugin>())
+ {
+ 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<DbAwareScriptingPlugin*>(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<QVariant> 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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateScriptConfig</class>
+ <widget class="QWidget" name="PopulateScriptConfig">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>443</width>
+ <height>362</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <property name="initialSize" stdset="0">
+ <size>
+ <width>440</width>
+ <height>360</height>
+ </size>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0" colspan="2">
+ <widget class="QGroupBox" name="initCodeGroup">
+ <property name="title">
+ <string>Initialization code (optional)</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QPlainTextEdit" name="initCodeEdit">
+ <property name="cfg" stdset="0">
+ <string>PopulateScript.InitCode</string>
+ </property>
+ <property name="scriptingEdit" stdset="0">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QGroupBox" name="codeGroup">
+ <property name="title">
+ <string>Per step code</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QPlainTextEdit" name="codeEdit">
+ <property name="cfg" stdset="0">
+ <string>PopulateScript.Code</string>
+ </property>
+ <property name="scriptingEdit" stdset="0">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="langGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Language</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QComboBox" name="langCombo">
+ <property name="cfg" stdset="0">
+ <string>PopulateScript.Language</string>
+ </property>
+ <property name="ScriptingLangCombo" stdset="0">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Help</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="toolButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="openUrl" stdset="0">
+ <string>http://sqlitestudio.pl/wiki/index.php/Official_plugins#Script_.28built-in.29</string>
+ </property>
+ <property name="customIcon" stdset="0">
+ <string>help</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
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 <QVariant>
+
+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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateSequenceConfig</class>
+ <widget class="QWidget" name="PopulateSequenceConfig">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>279</width>
+ <height>67</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="startValueSpin">
+ <property name="cfg" stdset="0">
+ <string>PopulateSequence.StartValue</string>
+ </property>
+ <property name="minimum">
+ <number>-99999999</number>
+ </property>
+ <property name="maximum">
+ <number>99999999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="startValueLabel">
+ <property name="text">
+ <string>Start value:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="stepSpin">
+ <property name="cfg" stdset="0">
+ <string>PopulateSequence.Step</string>
+ </property>
+ <property name="minimum">
+ <number>-999999</number>
+ </property>
+ <property name="maximum">
+ <number>999999</number>
+ </property>
+ <property name="value">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="stepLabel">
+ <property name="text">
+ <string>Step:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
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 <QVariant>
+
+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<QVariant>& args = QList<QVariant>()) = 0;
+ virtual bool hasError(Context* context) const = 0;
+ virtual QString getErrorMessage(Context* context) const = 0;
+ virtual QVariant evaluate(const QString& code, const QList<QVariant>& args = QList<QVariant>(), QString* errorMessage = nullptr) = 0;
+ virtual QString getIconPath() const = 0;
+};
+
+class DbAwareScriptingPlugin : public ScriptingPlugin
+{
+ public:
+ virtual QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking = false) = 0;
+ virtual QVariant evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking = false, QString* errorMessage = nullptr) = 0;
+
+ QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args)
+ {
+ return evaluate(context, code, args, nullptr, true);
+ }
+
+ QVariant evaluate(const QString& code, const QList<QVariant>& 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 <QScriptEngine>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QDebug>
+
+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<QVariant>& 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<QVariant>& 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<QVariant>& 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<QString, QVariant> hash = value.toHash();
+ QHashIterator<QString, QVariant> it(hash);
+ QStringList list;
+ while (it.hasNext())
+ {
+ it.next();
+ list << it.key() + ": " + convertVariant(it.value(), true).toString();
+ }
+ return "{" + list.join(", ") + "}";
+ }
+ case QVariant::Map:
+ {
+ QMap<QString, QVariant> map = value.toMap();
+ QMapIterator<QString, QVariant> 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<ContextQt*>(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 <QHash>
+#include <QVariant>
+#include <QCache>
+#include <QScriptValue>
+#include <QScriptProgram>
+
+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<QVariant>& args, Db* db, bool locking = false, QString* errorMessage = nullptr);
+ QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& 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<QString,QScriptProgram> 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<QVariant>& args, Db* db, bool locking);
+ QVariant convertVariant(const QVariant& value, bool wrapStrings = false);
+
+ static const constexpr int cacheSize = 5;
+
+ ContextQt* mainContext = nullptr;
+ QList<Context*> 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
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png
Binary files 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 <QScriptContext>
+#include <QScriptEngine>
+
+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<QString, QVariant> ScriptingQtDbProxy::mapToHash(const QMap<QString, QVariant>& map)
+{
+ QHash<QString, QVariant> hash;
+ QMapIterator<QString, QVariant> it(map);
+ while (it.hasNext())
+ {
+ it.next();
+ hash[it.key()] = it.value();
+ }
+ return hash;
+}
+
+QVariant ScriptingQtDbProxy::evalInternal(const QString& sql, const QList<QVariant>& listArgs, const QMap<QString, QVariant>& 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<QVariant> evalResults;
+ SqlResultsRowPtr row;
+ while (results->hasNext())
+ {
+ row = results->next();
+ evalResults << QVariant(row->valueList());
+ }
+ return evalResults;
+ }
+}
+
+QVariant ScriptingQtDbProxy::evalInternalErrorResult(bool singleCell)
+{
+ QList<QVariant> result;
+ if (singleCell)
+ result << QVariant();
+
+ return result;
+}
+
+QVariant ScriptingQtDbProxy::eval(const QString& sql)
+{
+ return evalInternal(sql, QList<QVariant>(), QMap<QString, QVariant>(), false);
+}
+
+QVariant ScriptingQtDbProxy::eval(const QString& sql, const QList<QVariant>& args)
+{
+ return evalInternal(sql, args, QMap<QString, QVariant>(), false);
+}
+
+QVariant ScriptingQtDbProxy::eval(const QString& sql, const QMap<QString, QVariant>& args)
+{
+ return evalInternal(sql, QList<QVariant>(), args, false);
+}
+
+QVariant ScriptingQtDbProxy::eval(const QString& sql, const QList<QVariant>& args, const QScriptValue& func)
+{
+ return evalInternal(sql, args, QMap<QString, QVariant>(), false, &func);
+}
+
+QVariant ScriptingQtDbProxy::eval(const QString& sql, const QMap<QString, QVariant>& args, const QScriptValue& func)
+{
+ return evalInternal(sql, QList<QVariant>(), args, false, &func);
+}
+
+QVariant ScriptingQtDbProxy::onecolumn(const QString& sql, const QList<QVariant>& args)
+{
+ return evalInternal(sql, args, QMap<QString, QVariant>(), true);
+}
+
+QVariant ScriptingQtDbProxy::onecolumn(const QString& sql, const QMap<QString, QVariant>& args)
+{
+ return evalInternal(sql, QList<QVariant>(), 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 <QObject>
+#include <QScriptable>
+#include <QHash>
+#include <QList>
+#include <QVariant>
+
+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<QVariant>& listArgs, const QMap<QString, QVariant>& mapArgs, bool singleCell,
+ const QScriptValue* funcPtr = nullptr);
+ QVariant evalInternalErrorResult(bool singleCell);
+
+ static QHash<QString, QVariant> mapToHash(const QMap<QString, QVariant>& map);
+
+ Db* db = nullptr;
+ bool useDbLocking = false;
+
+ public slots:
+ QVariant eval(const QString& sql);
+ QVariant eval(const QString& sql, const QList<QVariant>& args);
+ QVariant eval(const QString& sql, const QMap<QString, QVariant>& args);
+ QVariant eval(const QString& sql, const QList<QVariant>& args, const QScriptValue& func);
+ QVariant eval(const QString& sql, const QMap<QString, QVariant>& args, const QScriptValue& func);
+ QVariant onecolumn(const QString& sql, const QList<QVariant>& args);
+ QVariant onecolumn(const QString& sql, const QMap<QString, QVariant>& 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<SqlContext*>(context)->errorText.clear();
+}
+
+QVariant ScriptingSql::evaluate(ScriptingPlugin::Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking)
+{
+ SqlContext* ctx = dynamic_cast<SqlContext*>(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<SqlContext*>(context)->errorText = result->getErrorText();
+ return QVariant();
+ }
+
+ return result->getSingleCell();
+}
+
+QVariant ScriptingSql::evaluate(const QString& code, const QList<QVariant>& 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<SqlContext*>(context)->variables[name] = value;
+}
+
+QVariant ScriptingSql::getVariable(ScriptingPlugin::Context* context, const QString& name)
+{
+ if (dynamic_cast<SqlContext*>(context)->variables.contains(name))
+ return dynamic_cast<SqlContext*>(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<SqlContext*>(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<QString,QVariant> 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<QVariant>& args, Db* db, bool locking);
+ QVariant evaluate(const QString& code, const QList<QVariant>& 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<Context*> 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
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png
Binary files 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 <QDebug>
+
+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 <QObject>
+
+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<PopulateEngine*>& 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<QVariant> 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 <QMutex>
+#include <QObject>
+#include <QRunnable>
+#include <QStringList>
+
+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<PopulateEngine*>& engines, qint64 rows, QObject *parent = 0);
+ ~PopulateWorker();
+
+ void run();
+
+ private:
+ bool isInterrupted();
+ bool beforePopulating();
+ void afterPopulating();
+
+ Db* db = nullptr;
+ QString table;
+ QStringList columns;
+ QList<PopulateEngine*> 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 <QTextStream>
+
+/** @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 <QList>
+#include <QAbstractTableModel>
+
+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<SqlResultsRowPtr> 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<QString> &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 <QList>
+#include <QString>
+
+class ReturnCode
+{
+ private:
+ quint16 retCode;
+ QList<QString> errorMessages;
+
+ public:
+ ReturnCode();
+ explicit ReturnCode(QString errorMessage);
+ ReturnCode(quint16 code, QString errorMessage);
+
+ void addMessage(QString errorMessage);
+ bool isOk();
+ QList<QString>& 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <cstring> //strlen()
+#include <climits> //ULONG_MAX
+#include <vector> //vector<bool>
+#include <string> //operator std::string()
+#include <algorithm> //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<unsigned long int>(sqrt(static_cast<double>(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<int>(mult) % 10;
+ carry = static_cast<int>(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 &dividend, const BigInt &divisor,
+ BigInt &quotient, 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<bool> 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <iostream> //ostream, istream
+#include <cmath> //sqrt()
+#include <string> //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 &dividend, const BigInt &divisor,
+ BigInt &quotient, 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <iostream>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <iostream>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <string>
+#include <cstdlib> // 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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> //string
+#include <fstream> //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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <string>
+#include <fstream>
+#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 <QDebug>
+
+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<QStringList> SchemaResolver::getGroupedIndexes(const QString &database)
+{
+ QStringList allIndexes = getIndexes(database);
+ return getGroupedObjects(database, allIndexes, SqliteQueryType::CreateIndex);
+}
+
+StrHash<QStringList> 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<SqliteTableRelatedDdl>();
+ 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<QString> 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<SqliteCreateTable>();
+ SqliteCreateVirtualTablePtr createVirtualTable = query.dynamicCast<SqliteCreateVirtualTable>();
+ 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<DataType> SchemaResolver::getTableColumnDataTypes(const QString& table, int expectedNumberOfTypes)
+{
+ return getTableColumnDataTypes("main", table, expectedNumberOfTypes);
+}
+
+QList<DataType> SchemaResolver::getTableColumnDataTypes(const QString& database, const QString& table, int expectedNumberOfTypes)
+{
+ QList<DataType> dataTypes;
+ SqliteCreateTablePtr createTable = getParsedObject(database, table, TABLE).dynamicCast<SqliteCreateTable>();
+ 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<QStringList> 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<SelectResolver::Column> resolvedColumns = getViewColumnObjects(database, view);
+ QStringList columns;
+ foreach (const SelectResolver::Column& col, resolvedColumns)
+ columns << col.displayName;
+
+ return columns;
+}
+
+QList<SelectResolver::Column> SchemaResolver::getViewColumnObjects(const QString& view)
+{
+ return getViewColumnObjects("main", view);
+}
+
+QList<SelectResolver::Column> SchemaResolver::getViewColumnObjects(const QString& database, const QString& view)
+{
+ QList<SelectResolver::Column> results;
+ SqliteQueryPtr query = getParsedObject(database, view, VIEW);
+ if (!query)
+ return results;
+
+ SqliteCreateViewPtr createView = query.dynamicCast<SqliteCreateView>();
+ if (!createView)
+ {
+ qDebug() << "Parsed query is not CREATE VIEW statement as expected.";
+ return results;
+ }
+
+ SelectResolver resolver(db, createView->select->detokenize());
+ QList<QList<SelectResolver::Column> > 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<SqliteCreateTable>();
+
+ // 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<SqliteQuery>(database, QString::null);
+}
+
+StrHash< SqliteCreateTablePtr> SchemaResolver::getAllParsedTables()
+{
+ return getAllParsedTables("main");
+}
+
+StrHash< SqliteCreateTablePtr> SchemaResolver::getAllParsedTables(const QString& database)
+{
+ return getAllParsedObjectsForType<SqliteCreateTable>(database, "table");
+}
+
+StrHash< SqliteCreateIndexPtr> SchemaResolver::getAllParsedIndexes()
+{
+ return getAllParsedIndexes("main");
+}
+
+StrHash< SqliteCreateIndexPtr> SchemaResolver::getAllParsedIndexes(const QString& database)
+{
+ return getAllParsedObjectsForType<SqliteCreateIndex>(database, "index");
+}
+
+StrHash< SqliteCreateTriggerPtr> SchemaResolver::getAllParsedTriggers()
+{
+ return getAllParsedTriggers("main");
+}
+
+StrHash< SqliteCreateTriggerPtr> SchemaResolver::getAllParsedTriggers(const QString& database)
+{
+ return getAllParsedObjectsForType<SqliteCreateTrigger>(database, "trigger");
+}
+
+StrHash< SqliteCreateViewPtr> SchemaResolver::getAllParsedViews()
+{
+ return getAllParsedViews("main");
+}
+
+StrHash< SqliteCreateViewPtr> SchemaResolver::getAllParsedViews(const QString& database)
+{
+ return getAllParsedObjectsForType<SqliteCreateView>(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<SqliteQueryPtr> 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<SqliteCreateTablePtr> 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<SqliteCreateTablePtr>& allParsedTables)
+{
+ QStringList tables;
+
+ QList<SqliteCreateTable::Constraint*> tableFks;
+ QList<SqliteCreateTable::Column::Constraint*> fks;
+ bool result = false;
+ for (SqliteCreateTablePtr createTable : allParsedTables)
+ {
+ // Check table constraints
+ tableFks = createTable->getForeignKeysByTable(table);
+ result = contains<SqliteCreateTable::Constraint*>(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<SqliteCreateTable::Column::Constraint*>(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::ObjectDetails> SchemaResolver::getAllObjectDetails()
+{
+ return getAllObjectDetails("main");
+}
+
+StrHash<SchemaResolver::ObjectDetails> 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<SqliteCreateIndexPtr> SchemaResolver::getParsedIndexesForTable(const QString& database, const QString& table)
+{
+ QList<SqliteCreateIndexPtr> 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<SqliteCreateIndex>();
+ 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<SqliteCreateIndexPtr> SchemaResolver::getParsedIndexesForTable(const QString& table)
+{
+ return getParsedIndexesForTable("main", table);
+}
+
+QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForTable(const QString& database, const QString& table, bool includeContentReferences)
+{
+ return getParsedTriggersForTableOrView(database, table, includeContentReferences, true);
+}
+
+QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForTable(const QString& table, bool includeContentReferences)
+{
+ return getParsedTriggersForTable("main", table, includeContentReferences);
+}
+
+QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForView(const QString& database, const QString& view, bool includeContentReferences)
+{
+ return getParsedTriggersForTableOrView(database, view, includeContentReferences, false);
+}
+
+QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForView(const QString& view, bool includeContentReferences)
+{
+ return getParsedTriggersForView("main", view, includeContentReferences);
+}
+
+QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForTableOrView(const QString& database, const QString& tableOrView,
+ bool includeContentReferences, bool table)
+{
+ QList<SqliteCreateTriggerPtr> 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<SqliteCreateTrigger>();
+ 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<SqliteCreateViewPtr> SchemaResolver::getParsedViewsForTable(const QString& database, const QString& table)
+{
+ QList<SqliteCreateViewPtr> 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<SqliteCreateView>();
+ 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<SqliteCreateViewPtr> SchemaResolver::getParsedViewsForTable(const QString& table)
+{
+ return getParsedViewsForTable("main", table);
+}
+
+void SchemaResolver::filterSystemIndexes(QStringList& indexes)
+{
+ Dialect dialect = db->getDialect();
+ QMutableListIterator<QString> 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<SqliteCreateTable>();
+ 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<SqliteCreateVirtualTable>();
+ 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<SqliteCreateTable>();
+ 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 <QStringList>
+
+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<QStringList> getGroupedIndexes(const QString& database = QString::null);
+ StrHash<QStringList> getGroupedTriggers(const QString& database = QString::null);
+ QSet<QString> 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<ObjectDetails> getAllObjectDetails();
+ StrHash<ObjectDetails> getAllObjectDetails(const QString& database);
+
+ QList<SqliteCreateIndexPtr> getParsedIndexesForTable(const QString& database, const QString& table);
+ QList<SqliteCreateIndexPtr> getParsedIndexesForTable(const QString& table);
+ QList<SqliteCreateTriggerPtr> getParsedTriggersForTable(const QString& database, const QString& table, bool includeContentReferences = false);
+ QList<SqliteCreateTriggerPtr> getParsedTriggersForTable(const QString& table, bool includeContentReferences = false);
+ QList<SqliteCreateTriggerPtr> getParsedTriggersForView(const QString& database, const QString& view, bool includeContentReferences = false);
+ QList<SqliteCreateTriggerPtr> getParsedTriggersForView(const QString& view, bool includeContentReferences = false);
+ QList<SqliteCreateViewPtr> getParsedViewsForTable(const QString& database, const QString& table);
+ QList<SqliteCreateViewPtr> 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<DataType> getTableColumnDataTypes(const QString& table, int expectedNumberOfTypes = -1);
+ QList<DataType> getTableColumnDataTypes(const QString& database, const QString& table, int expectedNumberOfTypes = -1);
+
+ StrHash<QStringList> getAllTableColumns(const QString& database = QString::null);
+
+ QStringList getViewColumns(const QString& view);
+ QStringList getViewColumns(const QString& database, const QString& view);
+ QList<SelectResolver::Column> getViewColumnObjects(const QString& view);
+ QList<SelectResolver::Column> 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<SqliteQueryPtr> getAllParsedObjects();
+ StrHash<SqliteQueryPtr> getAllParsedObjects(const QString& database);
+ StrHash<SqliteCreateTablePtr> getAllParsedTables();
+ StrHash<SqliteCreateTablePtr> getAllParsedTables(const QString& database);
+ StrHash<SqliteCreateIndexPtr> getAllParsedIndexes();
+ StrHash<SqliteCreateIndexPtr> getAllParsedIndexes(const QString& database);
+ StrHash<SqliteCreateTriggerPtr> getAllParsedTriggers();
+ StrHash<SqliteCreateTriggerPtr> getAllParsedTriggers(const QString& database);
+ StrHash<SqliteCreateViewPtr> getAllParsedViews();
+ StrHash<SqliteCreateViewPtr> getAllParsedViews(const QString& database);
+
+ static QString getSqliteMasterDdl(bool temp = false);
+ static QStringList getFkReferencingTables(const QString& table, const QList<SqliteCreateTablePtr>& 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<SqliteCreateTriggerPtr> getParsedTriggersForTableOrView(const QString& database, const QString& tableOrView, bool includeContentReferences, bool table);
+
+ template <class T>
+ StrHash<QSharedPointer<T>> getAllParsedObjectsForType(const QString& database, const QString& type);
+
+ Db* db = nullptr;
+ Parser* parser = nullptr;
+ bool ignoreSystemObjects = false;
+ Db::Flags dbFlags;
+};
+
+template <class T>
+StrHash<QSharedPointer<T>> SchemaResolver::getAllParsedObjectsForType(const QString& database, const QString& type)
+{
+ StrHash< QSharedPointer<T>> 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<T> 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<T>();
+ 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 <QDebug>
+#include <QHash>
+#include <QHashIterator>
+#include <QString>
+
+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::Column> SelectResolver::resolve(SqliteSelect::Core *selectCore)
+{
+ errors.clear();
+ return resolveCore(selectCore);
+}
+
+QList<QList<SelectResolver::Column> > SelectResolver::resolve(SqliteSelect *select)
+{
+ errors.clear();
+ QList<QList<SelectResolver::Column> > results;
+ foreach (SqliteSelect::Core* core, select->coreSelects)
+ {
+ results << resolveCore(core);
+ currentCoreResults.clear();
+ }
+
+ return results;
+}
+
+QList<SelectResolver::Column> SelectResolver::resolveAvailableColumns(SqliteSelect::Core *selectCore)
+{
+ errors.clear();
+ return resolveAvailableCoreColumns(selectCore);
+}
+
+QList<QList<SelectResolver::Column> > SelectResolver::resolveAvailableColumns(SqliteSelect *select)
+{
+ errors.clear();
+ QList<QList<SelectResolver::Column> > results;
+ foreach (SqliteSelect::Core* core, select->coreSelects)
+ results << resolveAvailableCoreColumns(core);
+
+ return results;
+}
+
+QSet<SelectResolver::Table> SelectResolver::resolveTables(SqliteSelect::Core *selectCore)
+{
+ QSet<Table> tables;
+ QList<Column> columns = resolveAvailableColumns(selectCore);
+ foreach (Column col, columns)
+ tables << col.getTable();
+
+ return tables;
+}
+
+QList<QSet<SelectResolver::Table> > SelectResolver::resolveTables(SqliteSelect *select)
+{
+ QList<QSet<Table> > results;
+ QList<QList<Column> > columnLists = resolveAvailableColumns(select);
+ foreach (QList<Column> columns, columnLists)
+ {
+ QSet<Table> tables;
+ foreach (Column col, columns)
+ tables << col.getTable();
+
+ results << tables;
+ }
+
+ return results;
+}
+
+QList<SelectResolver::Column> SelectResolver::translateToColumns(SqliteSelect* select, const TokenList& columnTokens)
+{
+ errors.clear();
+ QList<SelectResolver::Column> 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::Column> 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<SqliteSelect*>(selectCore->parentStatement());
+ if (select && select->coreSelects.size() > 1)
+ markCompoundColumns();
+
+ if (select && select->with)
+ markCteColumns();
+
+ return currentCoreResults;
+}
+
+QList<SelectResolver::Column> SelectResolver::resolveAvailableCoreColumns(SqliteSelect::Core* selectCore)
+{
+ QList<Column> columns;
+ if (selectCore->from)
+ columns = resolveJoinSource(selectCore->from);
+
+ SqliteSelect* select = dynamic_cast<SqliteSelect*>(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<SqliteSelect::Core*>(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<QString> existingDisplayNames;
+ QString originalName;
+ int i;
+
+ QMutableListIterator<Column> 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<Column> 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::Column> SelectResolver::resolveJoinSource(SqliteSelect::Core::JoinSource *joinSrc)
+{
+ QList<SelectResolver::Column> columnSources;
+ columnSources += resolveSingleSource(joinSrc->singleSource);
+ foreach (SqliteSelect::Core::JoinSourceOther* otherSrc, joinSrc->otherSources)
+ columnSources += resolveOtherSource(otherSrc);
+
+ return columnSources;
+}
+
+QList<SelectResolver::Column> SelectResolver::resolveSingleSource(SqliteSelect::Core::SingleSource *joinSrc)
+{
+ if (!joinSrc)
+ return QList<Column>();
+
+ 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<Column> 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::Column> SelectResolver::resolveSingleSourceSubSelect(SqliteSelect::Core::SingleSource *joinSrc)
+{
+ QList<Column> columnSources = resolveSubSelect(joinSrc->select);
+ applySubSelectAlias(columnSources, joinSrc->alias);
+ return columnSources;
+}
+
+QList<SelectResolver::Column> SelectResolver::resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc)
+{
+ return resolveSingleSource(otherSrc->singleSource);
+}
+
+QList<SelectResolver::Column> SelectResolver::resolveSubSelect(SqliteSelect *select)
+{
+ QList<Column> 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<Column> it(columnSources);
+ while (it.hasNext())
+ it.next().flags |= FROM_COMPOUND_SELECT;
+ }
+
+ return columnSources;
+}
+
+QList<SelectResolver::Column> SelectResolver::resolveView(const QString& database, const QString& name, const QString& alias)
+{
+ QList<Column> 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<SqliteCreateView>();
+ 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<SelectResolver::Column>& columns, const QString& alias)
+{
+ // If this subselect is aliased, then all source columns should be considered as from aliased table
+ QMutableListIterator<Column> 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 <QString>
+#include <QHash>
+#include <QStringList>
+
+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<Column> resolve(SqliteSelect::Core* selectCore);
+ QList<QList<Column> > resolve(SqliteSelect* select);
+
+ QList<Column> resolveAvailableColumns(SqliteSelect::Core* selectCore);
+ QList<QList<Column> > resolveAvailableColumns(SqliteSelect* select);
+
+ QSet<Table> resolveTables(SqliteSelect::Core* selectCore);
+ QList<QSet<Table> > 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<Column> 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<Column> resolveCore(SqliteSelect::Core* selectCore);
+ QList<Column> 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<Column> resolveJoinSource(SqliteSelect::Core::JoinSource* joinSrc);
+ QList<Column> resolveSingleSource(SqliteSelect::Core::SingleSource* joinSrc);
+ QList<Column> resolveSingleSourceSubSelect(SqliteSelect::Core::SingleSource* joinSrc);
+ QList<Column> resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc);
+ QList<Column> resolveSubSelect(SqliteSelect* select);
+ QList<Column> 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<Column>& 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<Column> 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<Table,QStringList> 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<Column> 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 <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QUrlQuery>
+
+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\n<b>Plugins loaded:</b>\n%2\n\n<b>Version:</b>\n%3\n\n<b>Operating System:</b>\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<bool,QString>(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<bool,QString>(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 <QObject>
+#include <QHash>
+
+class QNetworkAccessManager;
+class QNetworkReply;
+
+class API_EXPORT BugReporter : public QObject
+{
+ Q_OBJECT
+
+ public:
+ typedef std::function<void(bool success, const QString& data)> 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<QNetworkReply*,ResponseHandler> replyToHandler;
+ QHash<QNetworkReply*,QPair<bool,QString>> 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 <QDebug>
+
+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<CodeFormatterPlugin*> formatterPlugins = PLUGINS->getLoadedPlugins<CodeFormatterPlugin>();
+ 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<QString,QVariant> 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<QString,QVariant> config = CFG_CORE.General.ActiveCodeFormatter.get();
+ QHashIterator<QString,CodeFormatterPlugin*> 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<QString,QHash<QString,CodeFormatterPlugin*>> availableFormatters;
+ QHash<QString,CodeFormatterPlugin*> 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 <QList>
+#include <QSharedPointer>
+#include <QObject>
+#include <QStringList>
+
+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<Collation> CollationPtr;
+
+ virtual void setCollations(const QList<CollationPtr>& newCollations) = 0;
+ virtual QList<CollationPtr> getAllCollations() const = 0;
+ virtual QList<CollationPtr> 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 <QObject>
+#include <QVariant>
+#include <QHash>
+#include <QStringList>
+#include <QSharedPointer>
+#include <QDateTime>
+
+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<QString,QVariant> options;
+ };
+
+ typedef QSharedPointer<CfgDb> CfgDbPtr;
+
+ struct DbGroup;
+ typedef QSharedPointer<DbGroup> DbGroupPtr;
+
+ struct DbGroup
+ {
+ qint64 id;
+ QString referencedDbName;
+ QString name;
+ QList<DbGroupPtr> childs;
+ int order;
+ bool open = false;
+ };
+
+ struct SqlHistoryEntry
+ {
+ QString query;
+ QString dbName;
+ int rowsAffected;
+ int unixtime;
+ };
+
+ typedef QSharedPointer<SqlHistoryEntry> SqlHistoryEntryPtr;
+
+ struct DdlHistoryEntry
+ {
+ QString dbName;
+ QString dbFile;
+ QDateTime timestamp;
+ QString queries;
+ };
+
+ typedef QSharedPointer<DdlHistoryEntry> DdlHistoryEntryPtr;
+
+ struct ReportHistoryEntry
+ {
+ int id = 0;
+ bool isFeatureRequest = false;
+ int timestamp = 0;
+ QString title;
+ QString url;
+ };
+
+ typedef QSharedPointer<ReportHistoryEntry> 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<QString,QVariant> getAll() = 0;
+
+ virtual bool addDb(const QString& name, const QString& path, const QHash<QString, QVariant> &options) = 0;
+ virtual bool updateDb(const QString& name, const QString &newName, const QString& path, const QHash<QString, QVariant> &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<CfgDbPtr> dbList() = 0;
+ virtual CfgDbPtr getDb(const QString& dbName) = 0;
+
+ virtual void storeGroups(const QList<DbGroupPtr>& groups) = 0;
+ virtual QList<DbGroupPtr> 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<DdlHistoryEntryPtr> 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<ReportHistoryEntryPtr> 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 <QFileInfo>
+
+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 <QObject>
+#include <QList>
+#include <QHash>
+
+/** @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<QString, QVariant> &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<QString, QVariant> &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<QString, QVariant> &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<Db*> getDbList() = 0;
+
+ /**
+ * @brief Gives list of valid databases.
+ * @return List of open databases.
+ */
+ virtual QList<Db*> getValidDbList() = 0;
+
+ /**
+ * @brief Gives list of currently open databases.
+ * @return List of open databases.
+ */
+ virtual QList<Db*> 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 <QThreadPool>
+#include <QTextCodec>
+#include <QBuffer>
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+
+ExportManager::ExportManager(QObject *parent) :
+ PluginServiceBase(parent)
+{
+}
+
+ExportManager::~ExportManager()
+{
+ safe_delete(config);
+}
+
+QStringList ExportManager::getAvailableFormats(ExportMode exportMode) const
+{
+ QStringList formats;
+ for (ExportPlugin* plugin : PLUGINS->getLoadedPlugins<ExportPlugin>())
+ {
+ 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<ExportPlugin>())
+ 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<ExportPlugin>().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 <QObject>
+
+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 <tt>QList&lt;int&gt;</tt>.
+ */
+ 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<ExportManager::ExportProviderFlag,QVariant> providerData;
+ };
+
+ typedef QSharedPointer<ExportObject> 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<QString, QString>&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 <QString>
+#include <QHash>
+
+class API_EXPORT ExtraLicenseManager
+{
+ public:
+ ExtraLicenseManager();
+
+ bool addLicense(const QString& title, const QString& filePath);
+ bool removeLicense(const QString& title);
+ const QHash<QString,QString>& getLicenses() const;
+
+ private:
+ QHash<QString,QString> 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 <QList>
+#include <QSharedPointer>
+#include <QObject>
+#include <QStringList>
+
+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<QVariant(const QList<QVariant>& args, Db* db, bool& ok)> ImplementationFunction;
+
+ ImplementationFunction functionPtr;
+ };
+
+ virtual void setScriptFunctions(const QList<ScriptFunction*>& newFunctions) = 0;
+ virtual QList<ScriptFunction*> getAllScriptFunctions() const = 0;
+ virtual QList<ScriptFunction*> getScriptFunctionsForDatabase(const QString& dbName) const = 0;
+ virtual QList<NativeFunction*> getAllNativeFunctions() const = 0;
+
+ virtual QVariant evaluateScalar(const QString& name, int argCount, const QList<QVariant>& args, Db* db, bool& ok) = 0;
+ virtual void evaluateAggregateInitial(const QString& name, int argCount, Db* db, QHash<QString, QVariant>& aggregateStorage) = 0;
+ virtual void evaluateAggregateStep(const QString& name, int argCount, const QList<QVariant>& args, Db* db,
+ QHash<QString, QVariant>& aggregateStorage) = 0;
+ virtual QVariant evaluateAggregateFinal(const QString& name, int argCount, Db* db, bool& ok, QHash<QString, QVariant>& 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 <QDebug>
+
+CollationManagerImpl::CollationManagerImpl()
+{
+ init();
+}
+
+void CollationManagerImpl::setCollations(const QList<CollationManager::CollationPtr>& newCollations)
+{
+ collations = newCollations;
+ refreshCollationsByKey();
+ storeInConfig();
+ emit collationListChanged();
+}
+
+QList<CollationManager::CollationPtr> CollationManagerImpl::getAllCollations() const
+{
+ return collations;
+}
+
+QList<CollationManager::CollationPtr> CollationManagerImpl::getCollationsForDatabase(const QString& dbName) const
+{
+ QList<CollationPtr> 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<QString,QVariant> 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<QString,QVariant> 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<CollationPtr>& newCollations);
+ QList<CollationPtr> getAllCollations() const;
+ QList<CollationPtr> 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<CollationPtr> collations;
+ QHash<QString,CollationPtr> collationsByKey;
+ QHash<QString,ScriptingPlugin*> 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 <QtGlobal>
+#include <QDebug>
+#include <QList>
+#include <QDir>
+#include <QFileInfo>
+#include <QDataStream>
+#include <QRegExp>
+#include <QDateTime>
+#include <QSysInfo>
+#include <QtConcurrent/QtConcurrentRun>
+
+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<QString,QVariant> ConfigImpl::getAll()
+{
+ SqlQueryPtr results = db->exec("SELECT [group], [key], value FROM settings");
+
+ QHash<QString,QVariant> 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<QString,QVariant>& 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<QString,QVariant> &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::CfgDbPtr> ConfigImpl::dbList()
+{
+ QList<CfgDbPtr> 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<DbGroupPtr>& 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::DbGroupPtr> 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<QString>("text");
+}
+
+void ConfigImpl::addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile)
+{
+ QtConcurrent::run(this, &ConfigImpl::asyncAddDdlHistory, queries, dbName, dbFile);
+}
+
+QList<ConfigImpl::DdlHistoryEntryPtr> 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<DdlHistoryEntryPtr> 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<Config::ReportHistoryEntryPtr> ConfigImpl::getReportHistory()
+{
+ static_qstring(sql, "SELECT id, timestamp, title, url, feature_request FROM reports_history");
+
+ SqlQueryPtr results = db->exec(sql);
+
+ QList<ReportHistoryEntryPtr> 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<QString> tables = results->columnAsList<QString>(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<QString,QVariant> getAll();
+
+ bool addDb(const QString& name, const QString& path, const QHash<QString, QVariant> &options);
+ bool updateDb(const QString& name, const QString &newName, const QString& path, const QHash<QString, QVariant> &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<CfgDbPtr> dbList();
+ CfgDbPtr getDb(const QString& dbName);
+
+ void storeGroups(const QList<DbGroupPtr>& groups);
+ QList<DbGroupPtr> 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<DdlHistoryEntryPtr> 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<ReportHistoryEntryPtr> 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 <QCoreApplication>
+#include <QFileInfo>
+#include <QHash>
+#include <QHashIterator>
+#include <QPluginLoader>
+#include <QDebug>
+#include <QUrl>
+#include <db/invaliddb.h>
+
+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<QString,QVariant>(), permanent);
+}
+
+bool DbManagerImpl::addDb(const QString &name, const QString &path, const QHash<QString,QVariant>& 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<QString, QVariant> &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<InvalidDb*>(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<Db*> DbManagerImpl::getDbList()
+{
+ listLock.lockForRead();
+ QList<Db*> list = dbList;
+ listLock.unlock();
+ return list;
+}
+
+QList<Db*> DbManagerImpl::getValidDbList()
+{
+ QList<Db*> list = getDbList();
+ QMutableListIterator<Db*> it(list);
+ while (it.hasNext())
+ {
+ it.next();
+ if (!it.value()->isValid())
+ it.remove();
+ }
+
+ return list;
+}
+
+QList<Db*> DbManagerImpl::getConnectedDbList()
+{
+ QList<Db*> list = getDbList();
+ QMutableListIterator<Db*> 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<QString, QVariant>& 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<Config::CfgDbPtr> 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<Db*> DbManagerImpl::getInvalidDatabases() const
+{
+ return filter<Db*>(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<QString,QVariant> &options, QString* errorMessages)
+{
+ QList<DbPlugin*> dbPlugins = PLUGINS->getLoadedPlugins<DbPlugin>();
+ 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<Db*>(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<Db*>(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<Db*>(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<DbPlugin>())
+ return;
+
+ InvalidDb* invalidDb = nullptr;
+ DbPlugin* dbPlugin = dynamic_cast<DbPlugin*>(plugin);
+ QList<Db*> 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<DbPlugin>())
+ return;
+
+ DbPlugin* dbPlugin = dynamic_cast<DbPlugin*>(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 <QObject>
+#include <QList>
+#include <QHash>
+#include <QReadWriteLock>
+#include <QSharedPointer>
+
+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<QString, QVariant> &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<QString, QVariant> &options, bool permanent);
+ void removeDbByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive);
+ void removeDbByPath(const QString& path);
+ void removeDb(Db* db);
+ QList<Db*> getDbList();
+ QList<Db*> getValidDbList();
+ QList<Db*> 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<QString, QVariant> &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<Db*> 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<QString, QVariant> &options, QString* errorMessages = nullptr);
+
+ /**
+ * @brief Registered databases list. Both permanent and transient databases.
+ */
+ QList<Db*> dbList;
+
+ /**
+ * @brief Database ame to database instance mapping, with keys being case insensitive.
+ */
+ StrHash<Db*> 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<QString,Db*> 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 <QVariantList>
+#include <QHash>
+#include <QDebug>
+#include <QRegularExpression>
+#include <QFile>
+#include <QUrl>
+
+FunctionManagerImpl::FunctionManagerImpl()
+{
+ init();
+}
+
+void FunctionManagerImpl::setScriptFunctions(const QList<ScriptFunction*>& newFunctions)
+{
+ clearFunctions();
+ functions = newFunctions;
+ refreshFunctionsByKey();
+ storeInConfig();
+ emit functionListChanged();
+}
+
+QList<FunctionManager::ScriptFunction*> FunctionManagerImpl::getAllScriptFunctions() const
+{
+ return functions;
+}
+
+QList<FunctionManager::ScriptFunction*> FunctionManagerImpl::getScriptFunctionsForDatabase(const QString& dbName) const
+{
+ QList<ScriptFunction*> 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<QVariant>& 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<QString,QVariant>& 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<QVariant>& args, Db* db, QHash<QString,QVariant>& 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<QString,QVariant>& 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<QVariant>& 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<DbAwareScriptingPlugin*>(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<QString, QVariant>& aggregateStorage)
+{
+ ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang);
+ if (!plugin)
+ return;
+
+ DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(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<QVariant>& args, Db* db, QHash<QString, QVariant>& aggregateStorage)
+{
+ ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang);
+ if (!plugin)
+ return;
+
+ if (aggregateStorage.contains("error"))
+ return;
+
+ DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(plugin);
+
+ ScriptingPlugin::Context* ctx = aggregateStorage["context"].value<ScriptingPlugin::Context*>();
+ 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<QString, QVariant>& aggregateStorage)
+{
+ ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang);
+ if (!plugin)
+ {
+ ok = false;
+ return langUnsupportedError(name, argCount, func->lang);
+ }
+
+ ScriptingPlugin::Context* ctx = aggregateStorage["context"].value<ScriptingPlugin::Context*>();
+ if (aggregateStorage.contains("error"))
+ {
+ ok = false;
+ plugin->releaseContext(ctx);
+ return aggregateStorage["errorMessage"];
+ }
+
+ DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(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<FunctionManager::NativeFunction*> FunctionManagerImpl::getAllNativeFunctions() const
+{
+ return nativeFunctions;
+}
+
+QVariant FunctionManagerImpl::evaluateNativeScalar(NativeFunction* func, const QList<QVariant>& 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<QString,QVariant> 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<int>(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<QString,QVariant> 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<ScriptFunction::Type>(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<QVariant>& 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<QVariant>& 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<QVariant>& 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<QVariant>& 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<QVariant>& 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<DbAwareScriptingPlugin*>(plugin);
+
+ QString error;
+ QVariant result;
+
+ if (dbAwarePlugin)
+ result = dbAwarePlugin->evaluate(args[1].toString(), QList<QVariant>(), db, false, &error);
+ else
+ result = plugin->evaluate(args[1].toString(), QList<QVariant>(), &error);
+
+ if (!error.isEmpty())
+ {
+ ok = false;
+ return error;
+ }
+ return result;
+}
+
+QVariant FunctionManagerImpl::nativeLangs(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ UNUSED(db);
+
+ if (args.size() != 0)
+ {
+ ok = false;
+ return QVariant();
+ }
+
+ QStringList names;
+ for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ names << plugin->getLanguage();
+
+ return names.join(", ");
+}
+
+QVariant FunctionManagerImpl::nativeHtmlEscape(const QList<QVariant>& 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<QVariant>& 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<QVariant>& 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<QVariant>& 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<QVariant>& 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<QVariant>& 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<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md4);
+}
+
+QVariant FunctionManagerImpl::nativeMd4Hex(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md4).toByteArray().toHex();
+}
+
+QVariant FunctionManagerImpl::nativeMd5(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md5);
+}
+
+QVariant FunctionManagerImpl::nativeMd5Hex(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md5).toByteArray().toHex();
+}
+
+QVariant FunctionManagerImpl::nativeSha1(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha1);
+}
+
+QVariant FunctionManagerImpl::nativeSha224(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha224);
+}
+
+QVariant FunctionManagerImpl::nativeSha256(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha256);
+}
+
+QVariant FunctionManagerImpl::nativeSha384(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha384);
+}
+
+QVariant FunctionManagerImpl::nativeSha512(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha512);
+}
+
+QVariant FunctionManagerImpl::nativeSha3_224(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_224);
+}
+
+QVariant FunctionManagerImpl::nativeSha3_256(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_256);
+}
+
+QVariant FunctionManagerImpl::nativeSha3_384(const QList<QVariant>& args, Db* db, bool& ok)
+{
+ return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_384);
+}
+
+QVariant FunctionManagerImpl::nativeSha3_512(const QList<QVariant>& 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<int>(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 <QCryptographicHash>
+
+class SqlFunctionPlugin;
+class Plugin;
+class PluginType;
+
+class API_EXPORT FunctionManagerImpl : public FunctionManager
+{
+ Q_OBJECT
+
+ public:
+ FunctionManagerImpl();
+
+ void setScriptFunctions(const QList<ScriptFunction*>& newFunctions);
+ QList<ScriptFunction*> getAllScriptFunctions() const;
+ QList<ScriptFunction*> getScriptFunctionsForDatabase(const QString& dbName) const;
+ QList<NativeFunction*> getAllNativeFunctions() const;
+ QVariant evaluateScalar(const QString& name, int argCount, const QList<QVariant>& args, Db* db, bool& ok);
+ void evaluateAggregateInitial(const QString& name, int argCount, Db* db, QHash<QString, QVariant>& aggregateStorage);
+ void evaluateAggregateStep(const QString& name, int argCount, const QList<QVariant>& args, Db* db, QHash<QString, QVariant>& aggregateStorage);
+ QVariant evaluateAggregateFinal(const QString& name, int argCount, Db* db, bool& ok, QHash<QString, QVariant>& aggregateStorage);
+ QVariant evaluateScriptScalar(ScriptFunction* func, const QString& name, int argCount, const QList<QVariant>& args, Db* db, bool& ok);
+ void evaluateScriptAggregateInitial(ScriptFunction* func, Db* db,
+ QHash<QString, QVariant>& aggregateStorage);
+ void evaluateScriptAggregateStep(ScriptFunction* func, const QList<QVariant>& args, Db* db,
+ QHash<QString, QVariant>& aggregateStorage);
+ QVariant evaluateScriptAggregateFinal(ScriptFunction* func, const QString& name, int argCount, Db* db, bool& ok,
+ QHash<QString, QVariant>& aggregateStorage);
+ QVariant evaluateNativeScalar(NativeFunction* func, const QList<QVariant>& 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<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSqlFile(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeReadFile(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeWriteFile(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeScript(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeLangs(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeHtmlEscape(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeUrlEncode(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeUrlDecode(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeBase64Encode(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeBase64Decode(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeCryptographicFunction(const QList<QVariant>& args, Db* db, bool& ok, QCryptographicHash::Algorithm algo);
+ static QVariant nativeMd4(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeMd4Hex(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeMd5(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeMd5Hex(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha1(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha224(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha256(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha384(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha512(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha3_224(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha3_256(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha3_384(const QList<QVariant>& args, Db* db, bool& ok);
+ static QVariant nativeSha3_512(const QList<QVariant>& args, Db* db, bool& ok);
+
+ QList<ScriptFunction*> functions;
+ QHash<Key,ScriptFunction*> functionsByKey;
+ QList<NativeFunction*> nativeFunctions;
+ QHash<Key,NativeFunction*> 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 <QCoreApplication>
+#include <QDir>
+#include <QDebug>
+#include <QJsonArray>
+#include <QJsonValue>
+
+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<PluginType*> 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<Plugin*>(container->loader->instance());
+ GenericPlugin* genericPlugin = dynamic_cast<GenericPlugin*>(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<Plugin*>(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<ScriptingPlugin*>(plugin);
+ if (scriptingPlugin)
+ scriptingPlugins[scriptingPlugin->getLanguage()] = scriptingPlugin;
+}
+
+void PluginManagerImpl::removePluginFromCollections(Plugin* plugin)
+{
+ ScriptingPlugin* scriptingPlugin = dynamic_cast<ScriptingPlugin*>(plugin);
+ if (scriptingPlugin && scriptingPlugins.contains(scriptingPlugin->getLanguage()))
+ scriptingPlugins.remove(plugin->getName());
+}
+
+bool PluginManagerImpl::readMetaData(PluginManagerImpl::PluginContainer* container)
+{
+ if (container->loader)
+ {
+ QHash<QString, QVariant> 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<Plugin*> PluginManagerImpl::getLoadedPlugins(PluginType* type) const
+{
+ QList<Plugin*> 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<QString, QVariant> PluginManagerImpl::readMetaData(const QJsonObject& metaData)
+{
+ QHash<QString, QVariant> 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<Plugin*> PluginManagerImpl::getLoadedPlugins() const
+{
+ QList<Plugin*> 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<PluginManager::PluginDetails> PluginManagerImpl::getAllPluginDetails() const
+{
+ QList<PluginManager::PluginDetails> 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<PluginManager::PluginDetails> PluginManagerImpl::getLoadedPluginDetails() const
+{
+ QList<PluginManager::PluginDetails> results = getAllPluginDetails();
+ QMutableListIterator<PluginManager::PluginDetails> 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 <QPluginLoader>
+#include <QHash>
+
+class API_EXPORT PluginManagerImpl : public PluginManager
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * @brief Creates plugin manager.
+ */
+ PluginManagerImpl();
+
+ /**
+ * @brief Deletes plugin manager.
+ */
+ ~PluginManagerImpl();
+
+ void init();
+ void deinit();
+ QList<PluginType*> 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<Plugin*> getLoadedPlugins(PluginType* type) const;
+ ScriptingPlugin* getScriptingPlugin(const QString& languageName) const;
+ QHash<QString,QVariant> 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<Plugin*> getLoadedPlugins() const;
+ QStringList getLoadedPluginNames() const;
+ QList<PluginDetails> getAllPluginDetails() const;
+ QList<PluginDetails> 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<PluginDependency> dependencies;
+
+ /**
+ * @brief Names of plugins that this plugin conflicts with.
+ */
+ QStringList conflicts;
+ };
+
+ /**
+ * @brief List of plugins, both loaded and unloaded.
+ */
+ typedef QList<PluginContainer*> PluginContainerList;
+
+ /**
+ * @brief Scans plugin directories to find out available plugins.
+ *
+ * It looks in the following locations:
+ * <ul>
+ * <li> application_directory/plugins/
+ * <li> application_config_directory/plugins/
+ * <li> directory pointed by the SQLITESTUDIO_PLUGINS environment variable
+ * <li> directory compiled in as PLUGINS_DIR parameter of the compilation
+ * </ul>
+ *
+ * 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<T>(), 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<PluginType*> registeredPluginTypes;
+
+ /**
+ * @brief Table with plugin types as keys and list of plugins assigned for each type.
+ */
+ QHash<PluginType*,PluginContainerList> pluginCategories;
+
+ /**
+ * @brief Table with plugin names and containers assigned for each plugin.
+ */
+ QHash<QString,PluginContainer*> 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<QString,ScriptingPlugin*> 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 <QThreadPool>
+#include <QDebug>
+
+ImportManager::ImportManager()
+{
+}
+
+QStringList ImportManager::getImportDataSourceTypes() const
+{
+ QStringList types;
+ for (ImportPlugin* plugin : PLUGINS->getLoadedPlugins<ImportPlugin>())
+ types << plugin->getDataSourceTypeName();
+
+ return types;
+}
+
+ImportPlugin* ImportManager::getPluginForDataSourceType(const QString& dataSourceType) const
+{
+ for (ImportPlugin* plugin : PLUGINS->getLoadedPlugins<ImportPlugin>())
+ {
+ 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<ImportPlugin>().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 <QFlags>
+#include <QStringList>
+
+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<QString> NotifyManager::getRecentInfos() const
+{
+ return recentInfos;
+}
+
+QList<QString> NotifyManager::getRecentWarnings() const
+{
+ return recentWarnings;
+}
+
+QList<QString> 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 <QStringList>
+#include <QObject>
+
+class API_EXPORT NotifyManager : public QObject
+{
+ Q_OBJECT
+
+ DECLARE_SINGLETON(NotifyManager)
+
+ public:
+ explicit NotifyManager(QObject *parent = 0);
+
+ QList<QString> getRecentErrors() const;
+ QList<QString> getRecentWarnings() const;
+ QList<QString> 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 <QStringList>
+
+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<SqlFormatterPlugin*> formatterPlugins = PLUGINS->getLoadedPlugins<SqlFormatterPlugin>();
+ * @endcode
+ *
+ * To get list of plugin types use getPluginTypes().
+ *
+ * To get PluginType for given plugin interface use getPluginType<PluginInterfaceClass>().
+ *
+ * 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<PluginType*> 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<PluginDetails> getAllPluginDetails() const = 0;
+ virtual QList<PluginDetails> 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<Plugin*> getLoadedPlugins(PluginType* type) const = 0;
+
+ /**
+ * @brief Provides list of all loaded plugins.
+ * @return List of plugins.
+ */
+ virtual QList<Plugin*> 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<ScriptingPlugin>()
+ * 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<QString,QVariant> 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 <class T>
+ void registerPluginType(const QString& title, const QString& form = QString())
+ {
+ registerPluginType(new DefinedPluginType<T>(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 <class T>
+ PluginType* getPluginType() const
+ {
+ foreach (PluginType* type, getPluginTypes())
+ {
+ if (!dynamic_cast<DefinedPluginType<T>*>(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 <class T>
+ QList<T*> getLoadedPlugins() const
+ {
+ QList<T*> typedPlugins;
+ PluginType* type = getPluginType<T>();
+ if (!type)
+ return typedPlugins;
+
+ foreach (Plugin* plugin, getLoadedPlugins(type))
+ typedPlugins << dynamic_cast<T*>(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 <class T>
+ QStringList getLoadedPluginNames() const
+ {
+ QStringList names;
+ PluginType* type = getPluginType<T>();
+ 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<T>() 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<PluginType*> types = SQLiteStudio::getInstance()->getPluginManager()->getPluginTypes();
+ * @endcode
+ * or there's a slightly simpler way:
+ * @code
+ * QList<PluginType*> types = SQLITESTUDIO->getPluginManager()->getPluginTypes();
+ * @endcode
+ * or there is a very simplified method, using this macro:
+ * @code
+ * QList<PluginType*> 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 <QDebug>
+#include <QThreadPool>
+
+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<QString, PopulateEngine*>& 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<PopulateEngine*>& 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 <QObject>
+#include <QHash>
+#include <QStringList>
+
+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<QString, PopulateEngine*>& engines, qint64 rows);
+
+ private:
+ void error();
+ void deleteEngines(const QList<PopulateEngine*>& engines);
+
+ bool workInProgress = false;
+ Db* db = nullptr;
+ QString table;
+ QStringList columns;
+ QList<PopulateEngine*> 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 <QTemporaryDir>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QUrl>
+#include <QUrlQuery>
+#include <QDebug>
+#include <QCoreApplication>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonValue>
+#include <QProcess>
+#include <QThread>
+#include <QtConcurrent/QtConcurrent>
+
+#ifdef Q_OS_WIN32
+#include "JlCompress.h"
+#include <windows.h>
+#include <shellapi.h>
+#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<UpdateEntry> 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 <a href=\"%1\">User Manual</a> 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::UpdateEntry> UpdateManager::readMetadata(const QJsonDocument& doc)
+{
+ QList<UpdateEntry> 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 <QObject>
+#include <functional>
+#include <QProcess>
+
+class QNetworkAccessManager;
+class QNetworkReply;
+class QTemporaryDir;
+class QFile;
+
+class API_EXPORT UpdateManager : public QObject
+{
+ Q_OBJECT
+ public:
+ typedef std::function<bool(const QString& msg)> 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<UpdateEntry> 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<UpdateEntry> updatesToDownload;
+ QHash<QString,QString> 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<UpdateManager::UpdateEntry>& 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 <QProcessEnvironment>
+#include <QThreadPool>
+#include <QCoreApplication>
+
+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<ScriptingPlugin::Context*>();
+
+ NotifyManager::getInstance();
+
+ dbAttacherFactory = new DbAttacherDefaultFactory();
+
+ config = new ConfigImpl();
+ config->init();
+
+ pluginManager = new PluginManagerImpl();
+ dbManager = new DbManagerImpl();
+
+ pluginManager->registerPluginType<GeneralPurposePlugin>(QObject::tr("General purpose", "plugin category name"));
+ pluginManager->registerPluginType<DbPlugin>(QObject::tr("Database support", "plugin category name"));
+ pluginManager->registerPluginType<CodeFormatterPlugin>(QObject::tr("Code formatter", "plugin category name"), "formatterPluginsPage");
+ pluginManager->registerPluginType<ScriptingPlugin>(QObject::tr("Scripting languages", "plugin category name"));
+ pluginManager->registerPluginType<ExportPlugin>(QObject::tr("Exporting", "plugin category name"));
+ pluginManager->registerPluginType<ImportPlugin>(QObject::tr("Importing", "plugin category name"));
+ pluginManager->registerPluginType<PopulatePlugin>(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<DbManagerImpl*>(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<CodeFormatterPlugin>()) // 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<CodeFormatterPlugin>()) // 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 <QString>
+#include <QStringList>
+#include <QObject>
+
+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:
+ * <ul>
+ * <li>#SQLITESTUDIO - access point to all services (singleton instance of SQLiteStudio)</li>
+ * <li>#PLUGINS - quick access to PluginManager</li>
+ * <li>#DBLIST - quick access to DbManager</li>
+ * <li>#FUNCTIONS - quick access to FunctionManager</li>
+ * <li>#CFG - quick access to Config</li>
+ * </ul>
+ */
+
+
+/**
+ * @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<Db*> 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 <QDebug>
+
+// 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<SqliteIndexedColumn*>& columnsToUpdate)
+{
+ bool modified = false;
+ QString lowerName;
+ QMutableListIterator<SqliteIndexedColumn*> 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<TokenPtr> 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<SqliteUpdate::ColumnAndValue> 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<SqliteCreateIndexPtr> 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<SqliteCreateTriggerPtr> 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<SqliteQuery*> 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<SqliteCreateViewPtr> 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<SqliteSelect*>(query);
+ if (select)
+ return handleSelect(select);
+
+ SqliteUpdate* update = dynamic_cast<SqliteUpdate*>(query);
+ if (update)
+ return handleTriggerUpdate(update, trigName);
+
+ SqliteInsert* insert = dynamic_cast<SqliteInsert*>(query);
+ if (insert)
+ return handleTriggerInsert(insert, trigName);
+
+ SqliteDelete* del = dynamic_cast<SqliteDelete*>(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<SelectResolver::Column> 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<SqliteSelect>();
+ 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<SqliteSelect*> selects = stmt->getAllTypedStatements<SqliteSelect>();
+ SqliteExpr* expr = nullptr;
+ foreach (SqliteSelect* select, selects)
+ {
+ expr = dynamic_cast<SqliteExpr*>(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<SqliteCreateIndexPtr> parsedIndexesForTable = resolver.getParsedIndexesForTable(originalTable);
+ foreach (SqliteCreateIndexPtr index, parsedIndexesForTable)
+ sqls << index->detokenize();
+}
+
+void TableModifier::simpleHandleTriggers(const QString& view)
+{
+ SchemaResolver resolver(db);
+ QList<SqliteCreateTriggerPtr> 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<SqliteCreateTable>();
+ 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<SqliteIndexedColumn*>& 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<QString, QString> 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<QStringList>& 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<QStringList> TsvSerializer::deserialize(const QString& data)
+{
+ QList<QStringList> 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 <QStringList>
+
+class API_EXPORT TsvSerializer
+{
+ public:
+ static QString serialize(const QList<QStringList>& data);
+ static QString serialize(const QStringList& data);
+ static QList<QStringList> 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<SqliteCreateView>();
+
+ 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<SqliteCreateTriggerPtr> 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<QList<SelectResolver::Column> > 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<bool> 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 <QString>
+
+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<bool> 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<bool> sqlMandatoryFlags;
+
+ QStringList warnings;
+ QStringList errors;
+
+ /**
+ * @brief createView Original DDL.
+ */
+ SqliteCreateViewPtr createView;
+
+ QStringList newColumns;
+};
+
+#endif // VIEWMODIFIER_H